cd /news/developer-tools/clidoc-brings-tsdoc-style-documentat… · home › topics › developer-tools › article
[ARTICLE · art-142728] src=ben3d.ca ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

CliDoc Brings TSDoc-Style Documentation to CLIs

Ben Houston released CliDoc, a Node tool that reads existing CLI command definitions from Yargs, Commander, or oclif and generates an OpenCLI JSON/YAML description, Markdown reference pages for Docusaurus and VitePress, and a `docgen` command that exposes the same description to AI agents. CliDoc implements the OpenCLI specification for Node and is already used by Houston's five command-line tools, including mtlx, node-prewarm, HDRify, git-dedup, and webgpu-bench.

by read7 min views1 publishedSep 30, 2026

CliDoc generates an OpenCLI description of your CLI from the command definitions you already wrote, then publishes reference docs to Docusaurus and VitePress, and gives AI agents the same description through a docgen command.

Ben Houston • • 7 min read

I build a lot of command-line tools: mtlx, node-prewarm, HDRify, git-dedup, and webgpu-bench. Each one needs reference documentation, and writing it by hand is tedious work that repeats what the command definitions already say.

So I wrote CliDoc. It reads your CLI's command definitions, produces a structured description of the whole tool, and turns that into reference documentation. All five tools above now use it.

TSDoc, but for CLIs# #

Library code solved this years ago. With TSDoc or JSDoc, the comment next to a function becomes its reference page. Web APIs have the same thing in OpenAPI (Swagger): you describe an endpoint once, in code, and get a machine-readable spec, a reference site, client generators, and validators.

CLIs have pieces of this. oclif can generate a README from its commands, and there are man pages and --help. But each framework uses its own format, and --help is free-form text meant for a person at a terminal. There is no portable, framework-neutral description of a CLI that other tools can build on.

The OpenCLI specification provides one. It defines a JSON/YAML format for describing a CLI: commands, subcommands, arguments, flags, types, defaults, choices, examples, and exit codes. CliDoc implements OpenCLI for Node.

How It Works# #

clidoc takes your CLI definitions, builds an OpenCLI document, renders that document as Markdown pages, and publishes those pages to your docs site.

The framework packages read the commands you already wrote:

Package Reads
@clidoc/yargs Yargs command modules
@clidoc/commander Commander command trees
@clidoc/oclif oclif manifests

There is no second place to describe a command. Here is the validate command from CliDoc itself:

export const command = defineCommand({
  command: 'validate <input>',
  describe: 'Validate an OpenCLI JSON or YAML document',
  builder: (yargs) =>
    yargs.positional('input', {
      type: 'string',
      demandOption: true,
      describe: 'OpenCLI document filename',
    }),
  handler: async ({ input }) => {
    parse(await readFile(input, 'utf8'));
    process.stdout.write('Valid OpenCLI document\n');
  },
});

CliDoc turns that into this entry in the OpenCLI document:

"clidoc validate": {
  "summary": "Validate an OpenCLI JSON or YAML document",
  "args": [
    {
      "name": "input",
      "required": true,
      "type": "string",
      "summary": "OpenCLI document filename"
    }
  ]
}

and this Markdown page:

## clidoc validate

Validate an OpenCLI JSON or YAML document

### Usage

``` sh
clidoc validate <input> [--help] [--version]
Argument Type Required Description
input string Yes OpenCLI document filename

## Why This Matters for Agents[#](#why-this-matters-for-agents)

The command line is the natural interface for coding agents. Every agent can already run a shell command, so a CLI works as an agent tool without an SDK, a server, or a custom integration.

But an agent still has to learn the tool. Today that means running `--help`, parsing the output, running `--help` on each subcommand, and hoping the text is complete. It works, but it is slow and loses information.

CliDoc gives agents two better options:

- **Introspection.** Any CliDoc-enabled CLI has a`docgen` command. Running`mycli docgen` prints the full OpenCLI document: every command, argument, flag, type, default, and choice as JSON, in one call.
- **Online documentation.** The same document renders into a reference site with one page per command, which agents and people can read instead of guessing from help text.

Both come from the command definitions, so as long as you regenerate them on build, they match the code.

## Adding It to Your CLI[#](#adding-it-to-your-cli)

Install the packages for your framework. For Yargs:

pnpm add @clidoc/yargs @clidoc/core


Then register a `docgen` command that builds the document from the same commands the CLI runs:

``` python
import { readFileSync } from 'node:fs';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { createDocgenCommand, fromYargs } from '@clidoc/yargs';
import { infoFromPackageJson, type OpenCliDocument } from '@clidoc/core';
import { command as greet } from './commands/greet.js';

// title, binary name, and version come from package.json
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
const info = infoFromPackageJson(pkg);

const parser = yargs(hideBin(process.argv)).command(greet);

let document: OpenCliDocument;
parser.command(createDocgenCommand(() => document)).demandCommand();
// build the document once every command, including docgen, is registered
document = fromYargs(parser, info);

parser.parse();

Now your CLI can document itself:

mycli docgen                                         # JSON to stdout
mycli docgen --output cli.json
mycli docgen --format markdown --output reference.md

docgen is a normal, visible command, listed in --help like any other, so an agent exploring your CLI finds it on its own. It builds the document from command metadata and never runs your other command handlers.

Commander works the same way with fromCommander. oclif reads the manifest generated at build time, and its docgen command comes from the separate @clidoc/oclif/docgen entry point, so importing fromOclif doesn't require @oclif/core. Each framework has a guide and runnable demo.

I also wrote yargs-file-commands, which puts each Yargs command in its own file. CliDoc supports it directly, including subcommands loaded lazily through async builders.

Some things can't be read from framework metadata, such as examples, exit codes, or the license. Add them with mergeDocument inside the function you pass to docgen:

import { mergeDocument } from '@clidoc/core';

parser.command(
  createDocgenCommand(() =>
    mergeDocument(document, {
      commands: {
        'mycli greet': {
          examples: [{ title: 'Basic', content: 'mycli greet Ada' }],
          exitCodes: [{ code: 1, status: 'BAD_USER_INPUT_ERROR', summary: 'Missing name' }],
        },
      },
    }),
  ),
);

Publishing to Docusaurus and VitePress# #

Once you have an OpenCLI document, publishing is a plugin. The output is ordinary Markdown with front matter, plus sidebar entries, so it sits alongside your hand-written guides.

For Docusaurus, @clidoc/docusaurus generates the pages at build time. Set markdown.format to 'md' so Docusaurus doesn't parse generated descriptions as MDX:

module.exports = {
  markdown: { format: 'md' },
  plugins: [
    [clidocPlugin, { input: 'cli.json', outputDir: 'docs/generated-cli', basePath: '/cli' }],
  ],
};

For VitePress, @clidoc/vitepress writes the pages and returns the matching sidebar:

const cliSidebar = await writeVitePress(document, {
  outputDir: fileURLToPath(new URL('..', import.meta.url)),
  basePath: '/cli',
});

Each command gets its own page with a usage line, an argument table, and a flag table. Regenerate on every build and the reference stays current. The git-dedup docs use the Docusaurus plugin, and CliDoc's own CLI reference is generated from its own commands.

The Rest of the Toolbox# #

The @clidoc/cli package (npm install -g @clidoc/cli) provides a CLI for working with OpenCLI documents:

  • clidoc validate checks a JSON or YAML document against the OpenCLI schema offline, plus the main logical checks from the upstream Go validator.
  • clidoc markdown renders a document to Markdown.
  • clidoc completion generates standalone Bash, Zsh, and Fish completion scripts from a document.
  • clidoc mcp turns a document intoModel Context Protocol tool definitions and can serve them over stdio, so an MCP client can call your CLI's commands as typed tools. See theMCP guide .

There is also a GitHub Action that rejects invalid OpenCLI documents in pull requests.

From one description you get the reference docs, shell completions, and agent tools for a CLI.

Dogfooding# #

CliDoc documents itself. clidoc docgen describes the clidoc binary from its own command files, and the reference site is regenerated from that output on every release. Adopting CliDoc across my five tools also surfaced cases the first version didn't handle, such as required variadic flags and lazily loaded Yargs subcommands, which are now supported.

Try It# #

npm install -g @clidoc/cli

Start with the documentation at clidoc.dev, pick the guide for your framework, and add a docgen command. Then run mycli docgen and hand the output to your coding agent. The source is on GitHub under the MIT license and requires Node 22.12 or newer.

── more in #developer-tools 4 stories · sorted by recency
── more on @clidoc 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/clidoc-brings-tsdoc-…] indexed:0 read:7min 2026-09-30 · —