Publish agent skills index and ARD manifest in docs (#3078)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-21 11:27:38 +02:00
1 parent bcf5991167
commit 8ceb56bf42
9 files changed
+263

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/docs": minor
---
Added an agent skill under `/.well-known/agent-skills/` and an `ai-catalog.json` manifest to the docs.
+11
View File
@@ -69,6 +69,17 @@ A short prompt is usually enough:
Read https://docs.tabler.io/ui/components/card.md and build a card with a header, a footer and a table inside.
```
## Agent skill
Coding agents that support [Agent Skills](https://agentskills.io) can load a ready-made Tabler skill instead of a prompt. It tells the agent how to install Tabler, how a page is structured, where to read the markup of a component and which rules keep the output consistent.
- `https://docs.tabler.io/.well-known/agent-skills/index.json` - the discovery index, per the [Agent Skills Discovery RFC](https://github.com/cloudflare/agent-skills-discovery-rfc).
- `https://docs.tabler.io/.well-known/agent-skills/tabler/SKILL.md` - the skill itself.
To use it in a project, copy `SKILL.md` into your agent's skills directory, for example `.claude/skills/tabler/SKILL.md` for Claude Code. The index carries a `sha256` digest of the file, so a tool can check the copy is intact.
The same resources are also listed in `https://docs.tabler.io/.well-known/ai-catalog.json`, an [Agentic Resource Discovery](https://agenticresourcediscovery.org) manifest that registries use to index a site.
## Where the markup comes from
The examples are taken from the rendered page, not from the documentation source. A `.md` file gives you the same markup as the copy button on the html page, with every icon and helper already expanded to plain html.
+45
View File
@@ -0,0 +1,45 @@
// The agent skills the docs publish under /.well-known/agent-skills/ (Agent
// Skills Discovery RFC 0.2.0, https://github.com/cloudflare/agent-skills-discovery-rfc).
// The markdown lives next to this file; the `${site.*}` tokens in it are resolved
// here so the skill and the rest of the docs quote the same urls and cdn version.
import { createHash } from 'node:crypto'
import { site } from '@shared/lib/site'
import { cdnPackageSnippet } from './cdn-snippets.ts'
import tablerSource from './agent-skills/tabler/SKILL.md?raw'
export const AGENT_SKILLS_SCHEMA = 'https://schemas.agentskills.io/discovery/0.2.0/schema.json'
const tokens: Record<string, string> = {
'${site.docsUrl}': site.docsUrl,
'${site.cdnUrl}': site.cdnUrl,
'${site.previewUrl}': site.previewUrl,
// same tags as the docs and llms.txt, with the SRI attributes when the hashes are current
'${cdnPackageSnippet()}': cdnPackageSnippet(),
}
const resolveTokens = (text: string) => Object.entries(tokens).reduce((result, [token, value]) => result.replaceAll(token, value), text)
const frontmatterField = (text: string, field: string) => text.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'))?.[1]?.trim() ?? ''
export type AgentSkill = {
name: string
description: string
/** the exact bytes served at `url`; the digest is computed over them */
content: string
url: string
digest: string
}
const define = (source: string): AgentSkill => {
const content = resolveTokens(source)
const name = frontmatterField(content, 'name')
return {
name,
description: frontmatterField(content, 'description'),
content,
url: `/.well-known/agent-skills/${name}/SKILL.md`,
digest: `sha256:${createHash('sha256').update(content).digest('hex')}`,
}
}
export const agentSkills: AgentSkill[] = [define(tablerSource)]
+103
View File
@@ -0,0 +1,103 @@
---
name: tabler
description: Build admin dashboards and app UIs with Tabler, the open source UI kit built on Bootstrap 5. Use when a project uses Tabler or @tabler/core, or when asked for a dashboard, admin panel, settings page, data table or form styled with Tabler. Covers installation, the page layout, where to read the real component markup and the rules that keep the output consistent.
---
# Tabler
Tabler is a free, MIT-licensed UI kit for admin dashboards built on Bootstrap 5. It ships compiled CSS and JavaScript in the `@tabler/core` npm package, and every Bootstrap 5 class and JavaScript behaviour keeps working underneath it.
## Read the docs, do not guess class names
The documentation at ${site.docsUrl} is published for agents:
- `${site.docsUrl}/llms.txt` lists every page with a one-line summary and a class reference for every component. Read it first to find the right page.
- Append `.md` to any docs url to get that page as markdown, for example `${site.docsUrl}/ui/components/card.md`. The file holds the prose and the exact markup of every example.
- `${site.docsUrl}/llms-full.txt` is the whole documentation in one file.
Before writing markup for a component, fetch its `.md` page and copy the example that is closest to the task. Only use classes that appear in the docs or in Bootstrap 5. Do not invent `tabler-*` classes.
## Install
Plain HTML, from the CDN:
```html
${cdnPackageSnippet()}
```
Put the stylesheet in `<head>` and the script at the end of `<body>`. When the tags carry `integrity` and `crossorigin` attributes, keep them unchanged; the hashes belong to this exact version. In a project with a bundler install the package and import the same files:
```shell
npm install @tabler/core
```
```js
import '@tabler/core/dist/css/tabler.min.css'
import '@tabler/core/dist/js/tabler.min.js'
```
Optional plugin stylesheets live next to the core file: `tabler-flags.min.css`, `tabler-socials.min.css`, `tabler-payments.min.css`, `tabler-vendors.min.css`, `tabler-marketing.min.css`, `tabler-themes.min.css`. Framework guides for React, Vue, Next.js, Nuxt, Angular, SvelteKit, Astro, Laravel, Rails, Django and Symfony are at `${site.docsUrl}/ui/getting-started/frameworks`.
## Page skeleton
Every app page follows the same structure. `${site.docsUrl}/ui/layout/page-layouts.md` has the complete markup for the horizontal, vertical and combined navigation variants.
```html
<div class="page">
<header class="navbar navbar-expand-md d-print-none">
<!-- brand, navigation, user menu -->
</header>
<div class="page-wrapper">
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<div class="page-pretitle">Overview</div>
<h2 class="page-title">Dashboard</h2>
</div>
<div class="col-auto ms-auto d-print-none">
<!-- page actions -->
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<!-- cards -->
</div>
</div>
</div>
<footer class="footer footer-transparent d-print-none">
<div class="container-xl"><!-- footer links --></div>
</footer>
</div>
</div>
```
- A sidebar is `<aside class="navbar navbar-vertical navbar-expand-lg">` placed before `.page-wrapper`.
- Content goes in cards: `.card` with `.card-header`, `.card-body`, `.card-footer`. Put cards in `.row.row-deck.row-cards` so they align in height.
- Use `.container-xl` for boxed pages and `.container-fluid` for full-width ones.
## Icons
Icons are inline SVGs from Tabler Icons (`https://tabler.io/icons`, also the `@tabler/icons` packages). Give the `<svg>` the `icon` class, and add `icon-sm`, `icon-md` or `icon-lg` for size. In a button or link the icon goes before the text. Decorative icons get `aria-hidden="true"`.
## Colour modes and theming
Set `data-bs-theme="dark"`, `"light"` or `"auto"` on `<html>` to pick the colour mode. `data-bs-theme-base`, `data-bs-theme-font`, `data-bs-theme-primary` and `data-bs-theme-radius` adjust the gray shade, font family, primary colour and corner radius. Details are at `${site.docsUrl}/ui/getting-started/color-modes.md` and `${site.docsUrl}/ui/getting-started/customize.md`.
Colours are Bootstrap-style names: `primary`, `secondary`, `success`, `warning`, `danger`, `info` plus Tabler's palette (`blue`, `azure`, `indigo`, `purple`, `pink`, `red`, `orange`, `yellow`, `lime`, `green`, `teal`, `cyan`). They work as `bg-{color}`, `text-{color}`, `btn-{color}`, `badge bg-{color}`, `alert-{color}` and so on.
## Interactive components
Dropdowns, modals, offcanvas, tooltips, popovers, toasts, tabs and collapse use the Bootstrap 5 data API: `data-bs-toggle`, `data-bs-target`, `data-bs-dismiss`. Tooltips and popovers are initialised automatically by `tabler.min.js` when the element has `data-bs-toggle="tooltip"` or `data-bs-toggle="popover"`. No extra JavaScript is needed for the rest.
## Rules of thumb
- Reach for a Tabler component before writing custom CSS. Spacing, colour and typography come from Bootstrap utilities (`mb-3`, `text-secondary`, `fw-bold`).
- Wrap groups of buttons, badges, avatars or tags in `.btn-list`, `.badge-list`, `.avatar-list` or `.tag-list` instead of adding margins by hand.
- Forms use `.form-label`, `.form-control`, `.form-select`, `.form-check` and `.form-hint`, the same as Bootstrap.
- Tables use `.table` inside `.table-responsive`, often with `.card-table` when they fill a card.
- Keep markup accessible: real `<button>` elements, labels on inputs, `aria-label` on icon-only buttons.
- The live demo at ${site.previewUrl} shows every layout and component in use. When unsure how pieces fit together, look at the matching demo page.
@@ -0,0 +1,16 @@
// Agent Skills Discovery index: which skills this site publishes and where.
import type { APIRoute } from 'astro'
import { AGENT_SKILLS_SCHEMA, agentSkills } from '@lib/agent-skills'
export const prerender = true
export const GET: APIRoute = () => {
const body = {
$schema: AGENT_SKILLS_SCHEMA,
skills: agentSkills.map(({ name, description, url, digest }) => ({ name, type: 'skill-md', description, url, digest })),
}
return new Response(`${JSON.stringify(body, null, 2)}\n`, {
headers: { 'Content-Type': 'application/json; charset=utf-8' },
})
}
@@ -0,0 +1,14 @@
// The `tabler` skill, byte-identical to what index.json's digest was computed over.
import type { APIRoute } from 'astro'
import { agentSkills } from '@lib/agent-skills'
export const prerender = true
export const GET: APIRoute = () => {
const skill = agentSkills.find((entry) => entry.name === 'tabler')
if (!skill) return new Response('Not found', { status: 404 })
return new Response(skill.content, {
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
})
}
+60
View File
@@ -0,0 +1,60 @@
// Agentic Resource Discovery manifest (https://agenticresourcediscovery.org):
// the machine-readable resources this site offers, so registries can index
// them. Everything listed here already exists elsewhere on the site.
import type { APIRoute } from 'astro'
import { site } from '@shared/lib/site'
import { agentSkills } from '@lib/agent-skills'
export const prerender = true
const domain = new URL(site.docsUrl).hostname
const urn = (namespace: string, name: string) => `urn:air:${domain}:${namespace}:${name}`
export const GET: APIRoute = () => {
const body = {
specVersion: '1.0',
host: {
displayName: site.title,
identifier: `did:web:${domain}`,
url: site.docsUrl,
},
entries: [
{
identifier: urn('docs', 'llms-txt'),
displayName: 'Tabler documentation index (llms.txt)',
description: 'Index of every documentation page with a one-line summary, links to the markdown version of each page and a class reference for every component.',
type: 'text/markdown',
url: `${site.docsUrl}/llms.txt`,
representativeQueries: ['Which components does Tabler have?', 'What classes does the Tabler card component use?', 'Where is the Tabler documentation for forms?'],
},
{
identifier: urn('docs', 'llms-full-txt'),
displayName: 'Tabler documentation, full text (llms-full.txt)',
description: 'The whole Tabler documentation as a single markdown file, including the markup of every example.',
type: 'text/markdown',
url: `${site.docsUrl}/llms-full.txt`,
representativeQueries: ['Give me the complete Tabler documentation', 'How do I build a dashboard layout with Tabler?', 'Show the markup of a Tabler modal'],
},
...agentSkills.map((skill) => ({
identifier: urn('skills', skill.name),
displayName: `Agent skill: ${skill.name}`,
description: skill.description,
type: 'text/markdown',
url: `${site.docsUrl}${skill.url}`,
representativeQueries: ['Build an admin dashboard with Tabler', 'Install Tabler in my project', 'Which Tabler classes should I use for a settings page?'],
})),
{
identifier: urn('docs', 'sitemap'),
displayName: 'Sitemap',
description: 'Every html page of the documentation.',
type: 'application/xml',
url: `${site.docsUrl}/sitemap.xml`,
representativeQueries: ['List all Tabler documentation pages', 'Crawl the Tabler docs'],
},
],
}
return new Response(`${JSON.stringify(body, null, 2)}\n`, {
headers: { 'Content-Type': 'application/json; charset=utf-8' },
})
}
+2
View File
@@ -89,6 +89,8 @@ Every page is available as markdown by appending \`.md\` to its url, for example
The markup is taken from the rendered pages, so every example is plain html — the same markup the copy button on the page gives you.
An agent skill for building with Tabler is published at \`${site.docsUrl}/.well-known/agent-skills/tabler/SKILL.md\` (index: \`${site.docsUrl}/.well-known/agent-skills/index.json\`).
${sections.join('\n\n')}
${classReference}
+7
View File
@@ -9,6 +9,13 @@
"source": "/(dist|preview|img|static|css|js)/(.*)",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=3600, stale-while-revalidate=86400" }]
},
{
"source": "/.well-known/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Cache-Control", "value": "public, max-age=3600, stale-while-revalidate=86400" }
]
},
{
"source": "/(llms|llms-full)\\.txt",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=3600, stale-while-revalidate=86400" }]