Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docs-yml.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4255,6 +4255,17 @@
"docs.SkillsPageActionConfig": {
"type": "object",
"properties": {
"path": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Path to a directory of agent skills in this repo, resolved relative to the folder\ncontaining this docs.yml (`../` is allowed, e.g. a repo-root `.agents/skills/`\nfolder). Every subdirectory containing a `SKILL.md` is published as one skill: the CLI\nvalidates the bundle, generates the `/.well-known/skills/index.json` discovery\nmanifest, and uploads every file at `.well-known/skills/<skill-name>/…` so that\n`npx skills add https://<docs-domain>` works. Nothing is written back to the repo."
},
"title": {
"oneOf": [
{
Expand Down Expand Up @@ -4297,7 +4308,7 @@
"type": "null"
}
],
"description": "Source repository (e.g. GitHub or GitLab) shown as a \"View source\" button."
"description": "Deprecated and ignored; kept for backwards compatibility."
},
"install-command": {
"oneOf": [
Expand Down Expand Up @@ -4326,7 +4337,7 @@
}
},
"additionalProperties": false,
"description": "Configures the \"Install skills\" page action and its modal. Sites that ship agent skills\npoint at where those skills live and how to install them. If the docs site also serves a\n`/.well-known` skills manifest, the modal replaces the hand-listed `skills` array with the\nserved manifest; `title`, `description`, `learn-more-url`, `repository`, and\n`install-command` always come from this config."
"description": "Configures the \"Install skills\" page action and its modal. Sites that ship agent skills\npoint at where those skills live (`path`) and how to install them. If the docs site also\nserves a `/.well-known` skills manifest, the modal replaces the hand-listed `skills` array\nwith the served manifest; `title`, `description`, `learn-more-url`, and\n`install-command` always come from this config."
},
"docs.PageActionOptions": {
"type": "object",
Expand Down
17 changes: 13 additions & 4 deletions fern/apis/docs-yml/definition/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -505,11 +505,20 @@ types:
SkillsPageActionConfig:
docs: |
Configures the "Install skills" page action and its modal. Sites that ship agent skills
point at where those skills live and how to install them. If the docs site also serves a
`/.well-known` skills manifest, the modal replaces the hand-listed `skills` array with the
served manifest; `title`, `description`, `learn-more-url`, `repository`, and
point at where those skills live (`path`) and how to install them. If the docs site also
serves a `/.well-known` skills manifest, the modal replaces the hand-listed `skills` array
with the served manifest; `title`, `description`, `learn-more-url`, and
`install-command` always come from this config.
properties:
path:
type: optional<string>
docs: |
Path to a directory of agent skills in this repo, resolved relative to the folder
containing this docs.yml (`../` is allowed, e.g. a repo-root `.agents/skills/`
folder). 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 at `.well-known/skills/<skill-name>/…` so that
`npx skills add https://<docs-domain>` works. Nothing is written back to the repo.
title:
type: optional<string>
docs: Overrides the modal title.
Expand All @@ -521,7 +530,7 @@ types:
docs: Optional "Learn more" link shown alongside the description.
repository:
type: optional<string>
docs: Source repository (e.g. GitHub or GitLab) shown as a "View source" button.
docs: Deprecated and ignored; kept for backwards compatibility.
"install-command":
type: optional<SkillsInstallCommand>
docs: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
- summary: |
Agent skills can now be declared in docs.yml via `page-actions.options.skills.path`,
pointing at a directory of skills in the docs repo (resolved relative to the folder
containing docs.yml; `../` is allowed). At `fern generate --docs`, every subdirectory
containing a SKILL.md is published as one skill: the CLI validates the bundle
(kebab-case names matching directories, non-empty descriptions, unique names, no
references escaping a skill's directory), generates the `.well-known/skills/index.json`
discovery manifest, and uploads every file at `.well-known/skills/<name>/…` so that
`npx skills add https://<docs-domain>` works — without writing anything back to the
repo. A declared path wins over a hand-populated `fern/.well-known/skills/` folder
(warned on conflict); the raw well-known passthrough still applies when no path is
declared. The unused `repository` display field is deprecated on the skills page
action config — it is still accepted (so existing docs.yml files keep loading) but is
no longer rendered.
type: feat
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ describe("parseDocsConfiguration — page-actions.options.skills", () => {
title: "Install agent skills",
description: "Skills for authoring Fern docs, maintained in our skills repo.",
"learn-more-url": "https://buildwithfern.com/learn/docs/ai/agent-skills",
repository: "https://github.com/fern-api/skills",
"install-command": "npx skills add fern-api/skills --skill fern-docs",
skills: [
{
Expand All @@ -57,7 +56,6 @@ describe("parseDocsConfiguration — page-actions.options.skills", () => {
title: "Install agent skills",
description: "Skills for authoring Fern docs, maintained in our skills repo.",
learnMoreUrl: "https://buildwithfern.com/learn/docs/ai/agent-skills",
repository: "https://github.com/fern-api/skills",
installCommand: "npx skills add fern-api/skills --skill fern-docs",
skills: [
{
Expand Down Expand Up @@ -105,6 +103,38 @@ describe("parseDocsConfiguration — page-actions.options.skills", () => {
]);
});

it("resolves `path` against the docs.yml directory and keeps it off the FDR-bound config", async () => {
const parsed = await parseRawDocsYml({
instances: [],
navigation: [],
"page-actions": { options: { skills: { path: "./agent-skills" } } }
});

expect(parsed.pageActions?.options.skillsDirectory).toEqual("/fern/agent-skills");
// `path` is CLI-only: the docs site reads the served manifest, not the repo path
expect(parsed.pageActions?.options.skills).toEqual({});
});

it("resolves a `../` path (e.g. a repo-root .agents/skills folder shared with coding agents)", async () => {
const parsed = await parseRawDocsYml({
instances: [],
navigation: [],
"page-actions": { options: { skills: { path: "../.agents/skills" } } }
});

expect(parsed.pageActions?.options.skillsDirectory).toEqual("/.agents/skills");
});

it("leaves skillsDirectory undefined when no path is declared", async () => {
const parsed = await parseRawDocsYml({
instances: [],
navigation: [],
"page-actions": { options: { skills: {} } }
});

expect(parsed.pageActions?.options.skillsDirectory).toBeUndefined();
});

it("rejects a skill entry without a name", async () => {
await expect(
parseRawDocsYml({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ function convertPageActions(
custom: (pageActions.options?.custom ?? []).map((action) =>
convertCustomPageAction(action, absoluteFilepathToDocsConfig)
),
skills: convertSkillsPageAction(pageActions.options?.skills)
skills: convertSkillsPageAction(pageActions.options?.skills),
skillsDirectory: resolveFilepath(pageActions.options?.skills?.path, absoluteFilepathToDocsConfig)
}
};
}
Expand All @@ -455,7 +456,6 @@ function convertSkillsPageAction(
title: skills.title,
description: skills.description,
learnMoreUrl: skills.learnMoreUrl,
repository: skills.repository,
installCommand: skills.installCommand,
skills: skills.skills?.map((skill) => ({
name: skill.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export interface ParsedPageActionsConfig {
mcp: boolean;
custom: ParsedCustomPageAction[];
skills: CjsFdrSdk.docs.v1.commons.PageActionOptions["skills"];
/**
* Resolved `skills.path` — a same-repo directory of agent skills. At publish time the
* CLI discovers every subdirectory containing a SKILL.md, generates the index.json
* discovery manifest, and uploads the bundle at `.well-known/skills/…`. CLI-only:
* never sent to FDR (the docs site reads the served manifest, not this path).
*/
skillsDirectory: AbsoluteFilePath | undefined;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import type * as FernDocsConfig from "../../../index.js";

/**
* Configures the "Install skills" page action and its modal. Sites that ship agent skills
* point at where those skills live and how to install them. If the docs site also serves a
* `/.well-known` skills manifest, the modal replaces the hand-listed `skills` array with the
* served manifest; `title`, `description`, `learn-more-url`, `repository`, and
* point at where those skills live (`path`) and how to install them. If the docs site also
* serves a `/.well-known` skills manifest, the modal replaces the hand-listed `skills` array
* with the served manifest; `title`, `description`, `learn-more-url`, and
* `install-command` always come from this config.
*/
export interface SkillsPageActionConfig {
/**
* Path to a directory of agent skills in this repo, resolved relative to the folder
* containing this docs.yml (`../` is allowed, e.g. a repo-root `.agents/skills/`
* folder). 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 at `.well-known/skills/<skill-name>/…` so that
* `npx skills add https://<docs-domain>` works. Nothing is written back to the repo.
*/
path?: string;
/** Overrides the modal title. */
title?: string;
/** Overrides the modal description/blurb. */
description?: string;
/** Optional "Learn more" link shown alongside the description. */
learnMoreUrl?: string;
/** Source repository (e.g. GitHub or GitLab) shown as a "View source" button. */
/** Deprecated and ignored; kept for backwards compatibility. */
repository?: string;
/**
* Command(s) used to install the skills, rendered verbatim as a copyable block. Providing a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const SkillsPageActionConfig: core.serialization.ObjectSchema<
serializers.SkillsPageActionConfig.Raw,
FernDocsConfig.SkillsPageActionConfig
> = core.serialization.object({
path: core.serialization.string().optional(),
title: core.serialization.string().optional(),
description: core.serialization.string().optional(),
learnMoreUrl: core.serialization.property("learn-more-url", core.serialization.string().optional()),
Expand All @@ -20,6 +21,7 @@ export const SkillsPageActionConfig: core.serialization.ObjectSchema<

export declare namespace SkillsPageActionConfig {
export interface Raw {
path?: string | null;
title?: string | null;
description?: string | null;
"learn-more-url"?: string | null;
Expand Down
Loading
Loading