Docs App Commands
OAT includes a dedicated docs command family for bootstrapping and maintaining documentation apps. Two frameworks are supported: Fumadocs (Next.js-based) and MkDocs Material.
Quick Look
- What it does: documents the docs-specific CLI surface for scaffolding apps, migrating markdown, generating Fumadocs app-root index manifests, and syncing MkDocs navigation.
- When to use it: when you already know you are working on a docs surface and need the exact command-level behavior.
- Primary commands:
oat docs init,oat docs migrate,oat docs generate-index,oat docs nav sync
Command surface
| Command | Purpose |
|---|---|
oat docs init | Scaffold a new docs app (Fumadocs or MkDocs). |
oat docs migrate | Convert MkDocs admonitions to GFM callouts and inject frontmatter. |
oat docs generate-index | Generate a Fumadocs app-root docs index manifest from the Markdown file tree. |
oat docs nav sync | Regenerate MkDocs mkdocs.yml navigation from directory index.md maps. |
oat docs analyze | CLI entrypoint that points users to the oat-docs-analyze skill. |
oat docs apply | CLI entrypoint that points users to the oat-docs-apply skill. |
Which Generation Command To Run
Use the framework-specific generated-artifact command:
- Fumadocs apps run
fumadocs-mdxandoat docs generate-index. In this repo,predevandprebuildregenerateapps/oat-docs/index.mdfromapps/oat-docs/docs. - MkDocs apps use
oat docs nav syncto regenerate thenav:block inmkdocs.ymlfrom authored directoryindex.md## Contentssections.
Both frameworks keep authored ## Contents sections as the source of local discovery. The generated artifact differs by framework.
oat docs init
Use oat docs init to scaffold a docs app that follows the OAT docs contract.
Consider the
oat-docs-bootstrapskill instead. The skill wrapsoat docs initwith preflight detection, richer input gathering (site name distinct from package name), capability-gated post-patches that close open CLI gaps (site-title metadata, Turbopack root, template-content fixes, docs-appAGENTS.mdbridge,## Contentslink extensions,contributing.mdthree-surfaces cleanup), build verification, config inspection, and a seven-section educational walkthrough. See Add Docs to a Repo §3a for the full flow. The CLI documented here remains the authoritative surface for flags and is the right choice when you need a deterministic, non-interactive scaffold (CI, automation).
Key behavior:
- prompts for framework choice (Fumadocs or MkDocs) in interactive mode
- detects monorepo vs single-package repo shape
- defaults to
apps/<app-name>for monorepos - defaults to
<app-name>/at repo root for single-package repos - sets
documentation.tooling,documentation.root, anddocumentation.indexin.oat/config.json - when the repo root exposes a compatible Turbo
scripts.build, patches it to exclude the new docs app from the default root build and adds a root-levelbuild:docsscript - prints a unified diff before writing the root
package.jsonchange and returns a manual snippet when the patch is skipped because the build script is missing, non-Turbo, or ambiguous
Fumadocs scaffold:
- thin Next.js app importing from
@open-agent-toolkit/docs-config,@open-agent-toolkit/docs-theme,@open-agent-toolkit/docs-transforms - static export (
output: 'export') with FlexSearch, Mermaid diagrams, dark/light mode predev/prebuildhooks runoat docs generate-indexautomatically- starter docs:
docs/index.md,docs/getting-started.md,docs/contributing.md
MkDocs scaffold:
- MkDocs Material with OAT contributor contract (unchanged from previous behavior)
- includes
docs/index.md,docs/contributing.md, and the local tooling needed to run the app
Supported flags:
--app-name <name>--target-dir <path>--framework <fumadocs|mkdocs>(default:fumadocsin non-interactive mode)--description <text>(site description, optional)--lint <none|markdownlint-cli2>--format <oxfmt|none>--no-root-patch--yes
Examples:
# Interactive (prompts for framework choice)
oat docs init --app-name my-docs
# Fumadocs (non-interactive)
oat docs init --app-name my-docs --framework fumadocs --yes
# MkDocs (non-interactive)
oat docs init --app-name my-docs --framework mkdocs --yesoat docs migrate
Use oat docs migrate to convert MkDocs-flavored markdown to GFM-compatible
format for Fumadocs.
Key behavior:
- converts MkDocs
!!!/???admonition syntax to GFM> [!TYPE]blockquote callouts - maps 14 MkDocs admonition types to 5 GFM types (NOTE, WARNING, TIP, IMPORTANT, CAUTION)
- injects
titlefrontmatter frommkdocs.ymlnav entries (falls back to first# heading, then filename) - seeds empty
description: ""frontmatter when missing - dry-run by default; use
--applyto write changes
Supported flags:
--docs-dir <path>(default:docs)--config <path>(path tomkdocs.ymlfor nav title extraction)--apply(write changes to disk; default is dry-run)
Example:
# Preview changes
oat docs migrate --docs-dir docs --config mkdocs.yml
# Apply changes
oat docs migrate --docs-dir docs --config mkdocs.yml --applyoat docs generate-index
Use oat docs generate-index to produce a generated Markdown manifest from the
docs file tree. In Fumadocs apps this is the app-root index.md, outside the
authored docs/ source tree. The generated index lists all pages with titles
and descriptions, organized by directory structure.
Key behavior:
- recursively walks the docs directory
- extracts page titles from frontmatter (falls back to first
# heading, then filename title-case) - includes descriptions from frontmatter when present
- resolves omitted paths from
.oat/config.jsonrather than the current directory:--docs-dirdefaults to<documentation.root>/docswhen that directory exists and to<documentation.root>otherwise, and--outputdefaults to the app-root manifest<documentation.root>/index.md - treats explicitly supplied
--docs-dir/--outputpaths as overrides resolved from the current directory - fails with exit code
2before generating or writing anything when an omitted path has no non-emptydocumentation.rootto resolve against, or when the configured root is not a directory - refuses unsafe output targets with exit code
1before generation, for derived and explicit paths alike, comparing symlink-resolved paths: inside the docs directory it indexes, equal todocumentation.config, or ending in.yml/.yamlin any case. A derived output whose existing file lacks theAUTOGENERATEDheader is refused too; naming that path with--outputoverwrites it explicitly. A refused target is an actionable flag error (1); unusable path configuration is a separate condition and exits2, so scripts can branch on the two - refuses a symlink chain that exceeds its own 32-hop cap, naming the path you supplied or the CLI derived rather than an intermediate link, and addressing the flag that owns it: an unusable output chain names
--outputand exits1; an unusable docs directory chain names--docs-dirand, when the directory was derived fromdocumentation.rootrather than passed explicitly, also names theoat config set documentation.root <path>repair and exits2as unusable path configuration. That cap governs the dangling links the command walks itself; a chain the operating system refuses first surfaces as its ownELOOPerror with exit1, which is what a too-deep chain reports on a platform whose own limit is 32 or lower - writes configuration only for the Fumadocs manifest transition — recording the manifest it just wrote inside
documentation.rootindocumentation.index, whendocumentation.toolingisfumadocsor the config declares neithertoolingnorconfig. MkDocs configurations are never written, anddocumentation.rootanddocumentation.configare never modified - prepends an
AUTOGENERATEDwarning comment to the output and rewrites the file on every run; do not hand-edit the generatedindex.md - should be freshness-checked against authored
docs/**/index.md## Contentsmaps before treating it as navigation evidence - sorting:
index.mdfirst, then directories before files, then lexical - reports the derived docs directory in human output and as
docsDir/docsDirSourceunder--json, alongsideexcludes— the effective exclusion list,documentation.excludesmerged with the--excludeflags exactly as it was handed to the generator - says where to look when a manifest is empty rather than reporting a bare
0 entries: which exclusion patterns were active, or that the docs directory held no indexable pages. The wording is observational — the command reports indexed entries, not pattern matches, so it names the exclusions without asserting they are the cause. An empty manifest is a report, not a failure — the file is still written, so a stale index is never left behind, and the command still exits0 - omits pages matching the exclusion list. Nothing is excluded by default; once listed, non-page Markdown such as
CLAUDE.mdorAGENTS.mdstops reaching the manifest. Patterns come fromdocumentation.excludesin.oat/config.jsonand from repeated--excludeflags, and the flags extend the configured list rather than replacing it. A directory left empty by exclusion emits no heading, and an empty list produces byte-identical output to no exclusions at all
Supported flags:
--docs-dir <path>(default:<documentation.root>/docs, falling back to<documentation.root>)--output <path>(default:<documentation.root>/index.md)--exclude <glob>(repeatable; additive todocumentation.excludes)
Exclusion patterns
Patterns match the path of each candidate relative to the docs directory being indexed — api/auth.md, api/nested — never an absolute or current-directory-relative path, and never the repository-relative path.
| Pattern | Matches |
|---|---|
CLAUDE.md | only the root-level CLAUDE.md; patterns are anchored |
**/CLAUDE.md | CLAUDE.md at any depth, including the docs root |
*.md | root-level Markdown only; * never crosses / |
drafts/ | the drafts directory and everything beneath it |
drafts | the same directory; the trailing / only forbids a file |
api/**/*.md | Markdown at any depth under api/, including api/x.md |
A trailing / restricts a pattern to directories and never matches a file; without it, a pattern that matches a directory path still prunes that directory. ** spans / only as a whole path segment — inside a segment (a**b) it is an ordinary single-segment wildcard. Matching is case-sensitive, / is the separator on every platform, and only * and ** are metacharacters — every other character, . included, is literal. A leading ./ or / is stripped, so both spellings anchor at the docs root.
Example:
# Resolve both paths from .oat/config.json
oat docs generate-index
# Portable explicit form, resolved from the current directory
oat docs generate-index --docs-dir apps/oat-docs/docs --output apps/oat-docs/index.md
# Persist the repository's own exclusions, then add a one-off
oat config set documentation.excludes "**/CLAUDE.md,**/AGENTS.md"
oat docs generate-index --exclude 'drafts/'See Documentation path resolution for the app-root meaning of documentation.root and the legacy source-root compatibility rule.
The Fumadocs scaffold runs this automatically via predev/prebuild npm
script hooks.
oat docs nav sync
Use nav sync in MkDocs apps after adding, removing, or renaming docs pages.
The command reads only the reserved ## Contents section from each directory
index.md and regenerates the nav: block in mkdocs.yml.
For Fumadocs apps, regenerate the root markdown manifest with oat docs generate-index instead.
Example:
oat docs nav sync --target-dir apps/oat-docsRelated reference:
oat docs analyze and oat docs apply
These CLI commands intentionally reserve the docs workflow surface without duplicating the skill logic in Commander handlers.
oat docs analyzeroutes users to theoat-docs-analyzeworkflowoat docs applyroutes users to theoat-docs-applyworkflow
Use the CLI entrypoints when you want discoverable command help. Use the skills when you want the actual docs analysis/apply execution flow.
Related docs: