diff --git a/.agents/skills/fern-docs/SKILL.md b/.agents/skills/fern-docs/SKILL.md new file mode 100644 index 000000000..3d5e974df --- /dev/null +++ b/.agents/skills/fern-docs/SKILL.md @@ -0,0 +1,154 @@ +--- +name: fern-docs +description: >- + Author and edit Fern documentation: MDX pages, navigation, docs.yml config, + custom components, landing pages, and changelog entries. Use when working in a + Fern docs repo (a fern/ directory with docs.yml). Routes to a detailed + reference per task. +--- + +# Fern docs authoring + +Write and edit documentation on a Fern site. `SKILL.md` is the index: follow the +principles below, then read the `references/` file that matches the +task before writing. Don't work from this page alone when a reference exists. + +**Scope: this skill governs Fern docs content only** — MDX pages, navigation, +`docs.yml` and related config, snippets, changelog entries, custom components, +and landing pages. It does **not** govern repo governance or tooling files that +happen to live in the same repo, such as `CLAUDE.md`, `AGENTS.md`, +`STYLE-GUIDE.md`, and contributor guides. Never apply the principles below — +"one canonical home," "prefer editing over creating," consolidation — to those +files. Leave them alone unless the user explicitly asks you to edit them. + +## Fern resources + +Fern's configuration evolves. Look things up rather than guessing. + +- **Docs:** https://buildwithfern.com/learn/docs/getting-started/overview.md + (append `.md` to any docs URL for clean markdown to fetch) +- **Page index:** https://buildwithfern.com/learn/llms.txt +- **MCP server** — connect your agent to Fern's live docs and query it for any + syntax or behavior you're unsure of. The endpoint works with any MCP client; + for Claude Code: + + ```bash + claude mcp add --transport http fern https://buildwithfern.com/learn/_mcp/server + ``` + +## Routing + +| Task | Read | +|------|------| +| Writing or editing a changelog entry | `references/changelog.md` | +| Setting up a product switcher, adding a product, or refactoring nav into products | `references/products.md` | +| Moving, renaming, or deleting a page, or any edit that changes a published URL | `references/redirects.md` | +| Restricting pages or content by role (RBAC), or wiring docs into an auth method (password, SSO, JWT, OAuth) | `references/authentication.md` | +| Editing the same content across multiple pages, or adding generic/boilerplate content (e.g. "contact support") | `references/snippets.md` | +| Translating pages into another language, adding a language, or updating existing translations (multi-language Fern repo) | `references/translations.md` | + +## Core principles + +- **Defer to the repo's own conventions.** When a docs repo has an established + pattern, voice, or structure — a `CLAUDE.md`, a style guide, or just the + consistent practice of surrounding pages — follow it over the guidance here. + This skill is the default, not an override. +- **Write what the reader needs to succeed — no more.** Every sentence earns its place. +- **Prefer editing over creating.** Search the repo for a page that already + covers the topic and update it instead of adding a duplicate. +- **Make minimal, precise edits.** Don't rewrite a page when a paragraph fix will do. +- **Push back when something seems wrong.** Explain why rather than complying silently. +- **Ask when unclear.** Don't fill gaps with assumptions. +- **Never fabricate.** If you don't know a config key or behavior, look it up + (MCP server or docs) or say so. Don't invent frontmatter or YAML fields. +- **Cross-reference.** When you mention a concept documented elsewhere, link to it + so readers can find their way. +- **Preserve URLs when they change.** Any edit that moves, renames, or deletes a + page — or changes a slug, section, or product — can change a published URL. + When it does, set up [redirects](https://buildwithfern.com/learn/docs/seo/redirects.md) + from the old path (see `references/redirects.md`) so inbound links and search + rankings survive. + +## Links + +Internal links are URL paths derived from **this repo's own `docs.yml` and page +frontmatter** — not file paths on disk or relative paths. Nearly every edit +touches a link, so get this right every time. + +A page URL is `//`: + +- **Base path** — the prefix in your docs URL (e.g. `/learn`, `/docs`; may be empty). +- **Product slug** — the product's `slug` in `fern/docs.yml`. Omitted for a + product with no `slug:` (such as a default single Home product). +- **``** — found by step 1 or step 2 below. + +To find ``: + +1. **Open the target page and check its frontmatter for `slug:`.** A frontmatter + slug is **absolute within the product**: it *is* `` on its own, and it + replaces the entire section/folder hierarchy. The section's slug does not + appear. Do not walk the navigation. +2. **Otherwise, walk `docs.yml`** within the product, from section through any + folders to the page. Each level contributes one slug: + - Its explicit `slug:` if set. + - Otherwise auto-derived from the display name: lowercased, spaces to + hyphens, special characters stripped (so `v3 (Latest)` becomes + `v-3-latest`). Auto-derivation isn't always obvious — verify, don't assume. + - In folder-based navigation, a page's slug comes from its **filename**, not + a display name (`quick-start.mdx` to `quick-start`). + - **Omit any level with `skip-slug: true`** — it contributes no segment. + +Example — page `./pages/ai/overview.mdx` under section "AI features", in product +`docs`, base path `/learn`: + +- with frontmatter `slug: ai-ai` → `/learn/docs/ai-ai` (the `ai-features` + section slug is dropped) +- with no frontmatter slug → `/learn/docs/ai-features/overview` (section + auto-slug `ai-features` + page auto-slug `overview`) + +Section slugs frequently differ from folder names, so never guess the URL from +the directory layout. If you can't resolve a path from `docs.yml` and the page's +frontmatter, say so rather than guessing. + +**Anchors** append `#heading`, and only resolve for real `##`/`###` Markdown +headings — not for JSX title props like ``, ``, +``, or ``. + +**Not internal links** (leave as-is): external `https://` URLs, image paths +(`./images/...`), snippet includes (``), and +same-page anchors (`#section`). + +## Cross-referencing + +When you document new functionality — a new page, or new behavior on an existing +one — related pages usually need a pointer to it, or the new content is hard to +find from where readers actually start. This applies to small edits too, not +just new pages. + +- **One canonical home.** The full explanation lives on one page; every other + page that touches the topic links to it instead of restating it. If you're + copying more than a sentence or two onto another page, it belongs on the + canonical page. +- **Find the targets.** Grep the docs for the feature name and one or two related + keywords, then read each hit and decide whether a pointer is warranted: + + ```bash + grep -rln "\|" fern --include="*.mdx" + ``` + + Common targets: pages for behavior the feature affects, adjacent feature pages, + and overview or landing pages. +- **Use the lightest form that works:** an inline link inside an existing + sentence, then a `` for a secondary aside, then a new `##` section, then + a new page. Inline links and Notes add no maintenance surface; new pages and + sections do. +- **Phrase inline links naturally — never "See [page]."** Put the link on a noun + phrase already in the sentence, not a tacked-on pointer sentence. Don't wrap a + lone link in a `` whose only job is to host it. + - Good: "Each request counts against your [rate limit](...), which resets hourly." + - Bad: "Requests are rate limited. See the [rate limits reference](...) for details." + - Bad: a standalone `` containing only "For more, see [rate limits](...)." +- **Frame by function, not plumbing.** Contrast what each feature does ("X + decides A; Y decides B"), not that they share a config key or sit in the same + directory. If the only thing linking two features is the same YAML key, it's + not worth a cross-reference. diff --git a/.agents/skills/fern-docs/references/authentication.md b/.agents/skills/fern-docs/references/authentication.md new file mode 100644 index 000000000..c86ad86ca --- /dev/null +++ b/.agents/skills/fern-docs/references/authentication.md @@ -0,0 +1,180 @@ +# Authentication & RBAC + +Gate a docs site so readers only see content meant for them. Read this when a +task involves restricting pages or content by role (RBAC), or wiring docs into an +auth method (password, SSO, JWT, OAuth). + +This page is decision context: what each piece is, which surface it lives on, and +**which canonical doc to fetch before you edit**. Don't work the syntax from +memory — fetch the matching page (append `.md` for clean markdown) or query the +Fern MCP server, since the config evolves. + +## First, locate the surface + +Most of an auth setup is *not* in the docs repo. Before touching anything, +work out which surface the task actually lives on — it changes whether you edit a +file at all: + +- **In the repo (`docs.yml` + MDX)** — declaring roles and gating content. This is + RBAC, and it's the bulk of what you'll edit here. +- **In the Fern Dashboard** — password protection (passwords mapped to roles). +- **With Fern support** — SSO, OAuth, and JWT signing secrets. You send Fern your + identity-provider details; Fern provisions the connection. **The repo never + holds client secrets or signing keys** — if a task asks you to put one in + `docs.yml`, push back. + +When a request is really "turn on SSO" or "set the docs password," the answer is a +pointer to the Dashboard or support, not a `docs.yml` edit. Say so rather than +inventing config. + +## RBAC (the in-repo part) + +RBAC has three moving parts, all in the repo. You'll usually touch more than one +in a single task, so know what each is for: + +- **Declare roles** — a top-level `roles:` list in `docs.yml`. Every role + referenced anywhere must be declared here or `fern check` fails. `everyone` is + auto-assigned to all visitors, including unauthenticated ones — but it is *not* + exempt from the declaration rule: if you reference it in `viewers:` or + ``, you must still list `everyone` under `roles:` or `fern check` + fails with "Role 'everyone' is used but not declared." +- **Gate navigation** — a `viewers:` key on a navigation item. Only listed roles + see it. Fern allows `viewers:` on **products, versions, tabs, sections, pages, + api references, and changelogs** — every level of the tree. +- **Gate inline content** — the `` component, to hide part of a + page rather than the whole page. + +Three behaviors that are easy to get backwards — get these right or the gating is +silently wrong: + +- **Omitting `viewers:` is authenticated-only, NOT public.** A navigation item + with no `viewers:` key is visible to *any signed-in user* but hidden from + logged-out visitors. To make something truly public you must set it explicitly: + `viewers: [everyone]`. "No key" and `[everyone]` are different states — don't + treat a bare item as public. +- **Viewership is inherited.** If a section is restricted to `admins`, all its + pages and nested sections are admins-only too — you don't repeat `viewers:` on + each child. So to test or reason about one level in isolation, keep its parents + permissive; otherwise an inherited restriction, not the key you're looking at, + is what's gating. +- **Gated items are hidden, not locked.** By default an unauthorized reader sees + no trace of the item in nav (not a 404 or lock screen), and unauthenticated + users are redirected to login. A "visible but locked" presentation exists but + must be requested from Fern, so don't assume it's on. + +**`viewers:` placement depends on the item.** On most items it sits directly on +the navigation entry, but **on a tab it goes in the `tabs:` map, not on the +`- tab:` entry** in `navigation:`. Putting it on the `- tab:` entry fails +`fern check`. + +**Fetch before editing:** +https://buildwithfern.com/learn/docs/authentication/features/rbac.md — for exact +`roles:`/`viewers:`/`` syntax. + +### Gating a level means that level must exist in the nav + +To gate, say, the tab or version level, the nav must actually contain a tab or a +version — and Fern's nav structure rules vary by file type in ways that bite when +you build a fixture or restructure to add a gate: + +- A **versioned product**'s version files use flat navigation (sections/pages); + **tabs are not supported there** — put tab-level structure in a non-versioned + product, whose product file does support `tabs:`. +- A **versioned product entry** in `docs.yml` needs both a top-level `path:` (the + default version) **and** a `versions:` list. +- An `- api:` reference node validates in a product's flat navigation but is + rejected inside a version file's tab layout. + +These are navigation-structure rules, not RBAC rules, but they surface the moment +you add a gate to a level that wasn't there before. Confirm structure against the +[products](https://buildwithfern.com/learn/docs/configuration/products.md) and +[versions](https://buildwithfern.com/learn/docs/configuration/versions.md) docs +(or `references/products.md`) rather than guessing — getting the wrong shape costs +far more time than looking it up. + +### You can't verify RBAC in local preview + +`fern docs dev` renders the site but does **not** enforce `viewers:` — every +reader is effectively unauthenticated, so restricted content just won't appear and +you can't confirm a role sees what it should. RBAC only takes effect on the +**published** site once an auth method is configured. Plan to verify on the +deployed site (or a preview instance) with real credentials, and tell the user +that local preview can't prove the gating works. + +## Auth methods — which one, and where it's configured + +Four ways to establish *who* a reader is; RBAC then decides what they see. JWT, +OAuth, and SSO all work through a `fern_token` browser cookie. + +| Method | Configured | RBAC | API key injection | Fetch when working on it | +|--------|-----------|------|-------------------|--------------------------| +| Password | Dashboard | ✅ up to 3 roles | ❌ | [`setup/password-protection.md`](https://buildwithfern.com/learn/docs/authentication/setup/password-protection.md) | +| SSO | Fern support | ❌ | ❌ | [`setup/sso.md`](https://buildwithfern.com/learn/docs/authentication/setup/sso.md) | +| OAuth | Fern support | ✅ | ✅ | [`setup/oauth.md`](https://buildwithfern.com/learn/docs/authentication/setup/oauth.md) | +| JWT | You (mint the token) | ✅ | ✅ | [`setup/jwt.md`](https://buildwithfern.com/learn/docs/authentication/setup/jwt.md) | + +Pick by the constraints, not preference: + +- **Password** — quick gate for a small set of audiences. **Capped at three + password-mapped roles**, no API key injection. Passwords (and their role + mapping) are set in the Fern Dashboard → Settings → Password card, not in the + repo. Each Dashboard role name must match a string in `docs.yml`'s `roles:` list + **exactly**, or the password won't unlock the content it's meant to. You don't + map a password to `everyone` — that's the built-in unauthenticated role. +- **SSO** — internal docs behind corporate login (SAML 2.0 / OIDC). No RBAC, no + API key injection — it's all-or-nothing access. +- **OAuth** — Fern runs the flow against your provider; supports RBAC and API key + injection. Choose over JWT when you'd rather not mint tokens yourself. +- **JWT** — you sign the `fern_token` yourself, so you control the payload. The + only method with meaningful repo/code involvement: roles travel in the token's + `fern` claim, and you can inject an API key into the API Explorer through it. + Fetch `setup/jwt.md` for the claim shape and a token-minting example, and + [`features/api-key-injection.md`](https://buildwithfern.com/learn/docs/authentication/features/api-key-injection.md) + for the injection payload schema. + +## Footguns + +- **Declare before you use.** Any role in `viewers:` or `` must be in the + top-level `roles:` list, or `fern check` fails. +- **Bare ≠ public.** An item with no `viewers:` is authenticated-only, not public. + Set `viewers: [everyone]` for anything that should reach logged-out visitors. +- **Inherited restrictions hide more than you think.** Because viewership cascades + down, a permissive-looking child can still be gated by an ancestor. Check the + whole path from product down, not just the item itself. +- **Gating without auth exposes everything.** `viewers:` and `` are + inert until an auth method is configured. On a published site with *no* auth + method, all gated content is publicly visible — *not* hidden, and not locked. + `fern check` passing does not mean content is protected. Never treat `viewers:` + as protection until auth is wired up and verified live. +- **Secrets never live in the repo.** Signing keys, client secrets, and passwords + belong in the Dashboard or with Fern support. +- **Gating ≠ moving.** Hiding a page with `viewers:` doesn't change its URL, so + it's not a redirect situation. Renaming or moving it still is — see + `references/redirects.md`. + +## Verify + +Run `fern check` after editing roles or `viewers:` — it catches undeclared roles +and malformed config. But neither `fern check` nor `fern docs dev` proves the +gating works: visibility is only enforced on the **published** site with an auth +method configured, so a passing check is necessary, not sufficient. + +Validating RBAC end-to-end happens mostly outside the repo. Walk the user through +the full loop and present it as concrete next steps: + +1. **Publish** — `fern generate --docs` (needs `fern login` or a `FERN_TOKEN`; add + `--no-prompt` for non-interactive runs). +2. **Configure an auth method** — declaring and gating in the repo enforces + nothing on its own. For a quick RBAC test, password protection is the fastest + path: in the Dashboard, add one password per role, with role names matching + `docs.yml` exactly. Fetch + https://buildwithfern.com/learn/dashboard/configuration/password-protection.md + for the current Dashboard steps and walk the user through them — don't recite + menus from memory, since the UI changes. +3. **Verify per role** — load the published site and enter each role's credential + in turn, confirming each role sees exactly its slice and *not* another's. Give + the user a role → expected-visible-content table so they know what correct + looks like. + +Always tell the user that local checks can't validate RBAC and that auth must be +live first. diff --git a/.agents/skills/fern-docs/references/changelog.md b/.agents/skills/fern-docs/references/changelog.md new file mode 100644 index 000000000..19e91aa79 --- /dev/null +++ b/.agents/skills/fern-docs/references/changelog.md @@ -0,0 +1,141 @@ +# Writing changelog entries + +A changelog entry announces a shipped change to users. Each entry is a dated +`.mdx` file; the filename date is the title. This reference covers how to *write* +an entry. For setup (folder location, `docs.yml` wiring, RSS, navigation), see +the official docs: https://buildwithfern.com/learn/docs/configuration/changelogs.md + +## File and naming + +- Entries go directly in the `changelog/` folder inside `fern/`. The folder must + be named `changelog` exactly. **No subdirectories** — nested files are silently + ignored. +- One entry per date, named by date. Supported filename formats: `YYYY-MM-DD` + (preferred, sorts cleanly), `MM-DD-YYYY`, `MM-DD-YY`. Always use `.mdx` (Fern + also accepts `.md`, but `.mdx` is the standard and unlocks the component + library). +- Sort order comes from the **filename date**, not frontmatter. A wrong filename + date puts the entry in the wrong position. +- The date is the title. **Do not add an h1.** +- If a file already exists for that date, add your feature as a new `##` section + in it rather than creating a second file. + +## Frontmatter tags + +Every entry needs a `tags` array. Tags drive the changelog's filtering, so use +specific, descriptive tags users would search for — by product area, feature, +release stage, platform, or impact: + +```mdx +--- +tags: ["voice-api", "breaking-change"] +--- +``` + +**Reuse existing tags** for consistency before adding a new one: + +```bash +grep -rh "^tags:" changelog/ | sort | uniq -c | sort -rn +``` + +A near-duplicate tag (`auth` when `security` already exists) fragments the +filter. + +## Entry format + +```mdx +--- +tags: ["search"] +--- + +## Filter search results by section + +You can now scope a search to a single section of your docs. Visitors pick a +section from the search bar, and results are limited to pages under it. + +- Available on all plans. +- Configured per docs instance. + + +``` + +- **One `##` per feature.** No h1. +- **Lead with the capability** — "You can now…" — not the implementation. +- **Keep it short:** 2–6 sentences. Bullets for detail lists, not the whole entry. +- **End with a Button** linking to the relevant docs page (recommended). +- For dashboard/UI changes, give the click-path (**Settings** > **Page access**) + before the Button. + +## The docs-link Button + +The `href` is a docs **URL path** built from `docs.yml`, not a file path: +`//`. + +```mdx + href="/changelog/../pages/search/configuration" + href="/docs/search-config/configuration" + href="/docs/customization/search" +``` + +See the **Links** section of `SKILL.md` for how to construct the path (base +path, product slug, frontmatter slugs, `skip-slug`) from this repo's `docs.yml`. + +## Voice + +Dry and direct. Cut hedges ("just", "simply", "make sure you") and filler ("in +order to", "so that"). At most one em dash per short paragraph. + +## Options + +Beyond a plain entry, the changelog supports: + +- **Overview page** — an `overview.mdx` in the `changelog/` folder renders above + all entries. `overview.mdx` is a reserved filename, so it is never treated as a + dated entry. +- **Components are available but avoid them.** `.mdx` entries *can* use Fern's + component library (``, ``, ``, etc.), but stacked across + a list of short entries they create visual noise. House rule: prose + the + ` diff --git a/fern/products/docs/pages/navigation/site-level-settings.mdx b/fern/products/docs/pages/navigation/site-level-settings.mdx index a317d9d76..6267615ab 100644 --- a/fern/products/docs/pages/navigation/site-level-settings.mdx +++ b/fern/products/docs/pages/navigation/site-level-settings.mdx @@ -871,19 +871,15 @@ Toggle the built-in actions on or off with a boolean. **Copy Page** (`copy-page` ### Install skills action -Add an "Install skills" button to the page action bar that opens a modal showing how to install your site's [custom skills](/learn/docs/ai-features/host-skills). The modal displays a copyable install command, the list of available skills, and links to the skill source. +Add an "Install skills" button to the page action bar that opens a modal showing how to install your site's [custom skills](/learn/docs/ai-features/host-skills). The modal displays a copyable install command and the list of available skills. Point `path` at a directory of skills in your repo and Fern publishes them and reads the modal's skill list from the generated manifest: ```yaml docs.yml page-actions: options: skills: + path: ../.agents/skills title: Plant API Skills description: Skills for working with the Plant Store API. - repository: https://github.com/your-org/plant-api-skills - skills: - - name: plant-care - description: Guide for diagnosing and treating common plant diseases. - url: https://github.com/your-org/plant-api-skills/tree/main/plant-care ``` diff --git a/fern/products/docs/snippets/install-skills-properties.mdx b/fern/products/docs/snippets/install-skills-properties.mdx index 0d71c5682..8377fd167 100644 --- a/fern/products/docs/snippets/install-skills-properties.mdx +++ b/fern/products/docs/snippets/install-skills-properties.mdx @@ -2,6 +2,10 @@ Enables the "Install skills" page action and configures the modal it opens. Omit to hide it. + + Path to a directory of agent skills in this repo. Resolved relative to the folder containing `docs.yml`; `../` is allowed (e.g. a repo-root `.agents/skills/` directory that also serves local coding agents). Every subdirectory containing a `SKILL.md` is published as one skill: the CLI validates the bundle, generates the `/.well-known/skills/index.json` discovery manifest, and uploads every file so that `npx skills add https://` works. Nothing is written back to your repo. + + Overrides the modal title. @@ -14,14 +18,10 @@ URL for a "Learn more" link shown alongside the description. - - Source repository URL displayed as a "View source" button in the modal. - - - Command(s) shown as a copyable block in the modal. A single string is shown as-is; an array is joined with newlines for multi-step installs. Defaults to `npx skills add https://` when omitted. + Command(s) shown as a copyable block in the modal. A single string is shown as-is; an array is joined with newlines for multi-step installs. Defaults to `npx skills add https://` when omitted. Set this when the generated command won't work as-is — for example auth-walled docs, a private registry, or a multi-step install. - List of skills displayed in the modal. Each entry has a `name` (required), `description` (optional), and `url` (optional link to the skill source). When the site serves a `/.well-known/agent-skills/index.json` or `/.well-known/skills/index.json` manifest, the manifest replaces this list. + List of skills displayed in the modal. Each entry has a `name` (required), `description` (optional), and `url` (optional link to the skill source). When `path` is set, or the site serves a `/.well-known/skills/index.json` (or `/.well-known/agent-skills/index.json`) manifest, the generated manifest replaces this hand-listed array.