Machine-readable help

CLIs are increasingly read by something other than a person: a script wiring two tools together, a package manager, a language model deciding which flag to pass. All of them start from --help, and --help is the worst possible source. It is written for a human at a terminal: it wraps to a width, carries ANSI styling, and expresses structure as layout, so a reader has to recover the option list from column alignment and guess where a description ends.

None of that parsing is necessary. The structure was there before it was formatted, and Click Extra hands it over directly.

click_extra.command_doc extracts a command once, into a CommandDoc, and renders that model several ways. Every command gets a --help-format FORMAT option reaching them, and click-extra wrap does the same for a CLI whose author never heard of Click Extra.

A program wants to know

Ask for

Documented in

What does this command do, and what does it accept?

--help-format json, --help-format markdown

this page

What are the parameters, their types, and where did each value come from?

--params

Parameters

What is the resolved configuration, as a file I can edit and replay?

--export-config FORMAT

Configuration

What subcommands exist?

--help-format json-full, or --tree for a person

Command tree

How do I complete this in a shell?

--help-format carapace

Carapace

from click_extra import Choice, argument, command, echo, option


@command(context_settings={"show_envvar": True})
@argument("city", help="Name of the city to report on.")
@option(
    "--units",
    type=Choice(["celsius", "fahrenheit"]),
    default="celsius",
    help="Temperature scale to display.",
)
def weather(city, units):
    """Report the current temperature for a city."""
    echo(f"{city}: 21 degrees {units}.")

The --help-format option

One option carrying a format, rather than a flag per format. A CLI’s option list is the most expensive real estate in its help screen, and every reader pays for it whether or not they will ever export anything: a family of --help-json, --help-markdown and --help-carapace flags would widen the label column of every screen, forever. Here a new format costs a registry entry and nothing on screen.

JSON

$ weather --help-format json
{
  "name": "weather",
  "short_help": "Report the current temperature for a city.",
  "version": null,
  "synopsis": "weather [OPTIONS] CITY",
  "description": "Report the current temperature for a city.",
  "arguments": [
    {
      "metavar": "CITY",
      "help": "Name of the city to report on."
    }
  ],
  "option_groups": [
    {
      "title": null,
      "help": null,
      "options": [
        {
          "names": [
            "--units"
          ],
          "spec": "--units [celsius|fahrenheit]",
          "metavar": "[celsius|fahrenheit]",
          "help": "Temperature scale to display.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--time",
            "--no-time"
          ],
          "spec": "--time / --no-time",
          "metavar": null,
          "help": "Measure and print elapsed execution time.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--config"
          ],
          "spec": "--config CONFIG_PATH",
          "metavar": "CONFIG_PATH",
          "help": "Location of the configuration file. Supports local path with glob patterns or remote URL.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--no-config"
          ],
          "spec": "--no-config",
          "metavar": null,
          "help": "Ignore all configuration files and only use command line parameters and environment variables.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--validate-config"
          ],
          "spec": "--validate-config FILE",
          "metavar": "FILE",
          "help": "Validate the configuration file and exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--export-config"
          ],
          "spec": "--export-config FORMAT",
          "metavar": "FORMAT",
          "help": "Export the configuration in the selected format to <stdout>, then exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--accessible"
          ],
          "spec": "--accessible",
          "metavar": null,
          "help": "Accessibility mode: disable colors and render tables in a borderless, screen-reader-friendly format.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--color"
          ],
          "spec": "--color[=auto|always|never]",
          "metavar": "[auto|always|never]",
          "help": "Colorize the output. A bare --color is the same as --color=always.",
          "required": false,
          "optional_value": true
        },
        {
          "names": [
            "--no-color"
          ],
          "spec": "--no-color",
          "metavar": null,
          "help": "Disable colorization (alias of --color=never).",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--progress",
            "--no-progress"
          ],
          "spec": "--progress / --no-progress",
          "metavar": null,
          "help": "Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by --accessible.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--theme"
          ],
          "spec": "--theme [auto|dark|dracula|light|manpage|monokai|nord|solarized_dark]",
          "metavar": "[auto|dark|dracula|light|manpage|monokai|nord|solarized_dark]",
          "help": "Color theme used for help screens.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--params"
          ],
          "spec": "--params",
          "metavar": null,
          "help": "Show all CLI parameters, their provenance, defaults and value, then exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--table-format"
          ],
          "spec": "--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]",
          "metavar": "[aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]",
          "help": "Rendering style of tables.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--verbosity"
          ],
          "spec": "--verbosity LEVEL",
          "metavar": "LEVEL",
          "help": "Either CRITICAL, ERROR, WARNING, INFO, DEBUG.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--verbose",
            "-v"
          ],
          "spec": "--verbose / -v",
          "metavar": null,
          "help": "Increase the default WARNING verbosity by one level for each additional repetition of the option.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--quiet",
            "-q"
          ],
          "spec": "--quiet / -q",
          "metavar": null,
          "help": "Decrease the default WARNING verbosity by one level for each additional repetition of the option.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--tree"
          ],
          "spec": "--tree",
          "metavar": null,
          "help": "Show the tree of nested subcommands and exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--man"
          ],
          "spec": "--man",
          "metavar": null,
          "help": "Read the command's manual page and exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--help-format"
          ],
          "spec": "--help-format [carapace|json|json-full|man|markdown|markdown-full]",
          "metavar": "[carapace|json|json-full|man|markdown|markdown-full]",
          "help": "Render the command in the given format and exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--version"
          ],
          "spec": "--version",
          "metavar": null,
          "help": "Show the version and exit.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "-h",
            "--help"
          ],
          "spec": "-h / --help",
          "metavar": null,
          "help": "Show this message and exit.",
          "required": false,
          "optional_value": false
        }
      ]
    }
  ],
  "subcommands": [],
  "examples": [],
  "environment": [
    {
      "variable": "WEATHER_UNITS",
      "help": "Temperature scale to display."
    },
    {
      "variable": "WEATHER_TIME",
      "help": "Measure and print elapsed execution time."
    },
    {
      "variable": "WEATHER_CONFIG",
      "help": "Location of the configuration file. Supports local path with glob patterns or remote URL."
    },
    {
      "variable": "WEATHER_VALIDATE_CONFIG",
      "help": "Validate the configuration file and exit."
    },
    {
      "variable": "WEATHER_EXPORT_CONFIG",
      "help": "Export the configuration in the selected format to <stdout>, then exit."
    },
    {
      "variable": "WEATHER_ACCESSIBLE",
      "help": "Accessibility mode: disable colors and render tables in a borderless, screen-reader-friendly format."
    },
    {
      "variable": "WEATHER_COLOR",
      "help": "Colorize the output. A bare --color is the same as --color=always."
    },
    {
      "variable": "WEATHER_NO_COLOR",
      "help": "Disable colorization (alias of --color=never)."
    },
    {
      "variable": "WEATHER_PROGRESS",
      "help": "Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by --accessible."
    },
    {
      "variable": "WEATHER_THEME",
      "help": "Color theme used for help screens."
    },
    {
      "variable": "WEATHER_PARAMS",
      "help": "Show all CLI parameters, their provenance, defaults and value, then exit."
    },
    {
      "variable": "WEATHER_TABLE_FORMAT",
      "help": "Rendering style of tables."
    },
    {
      "variable": "WEATHER_VERBOSITY",
      "help": "Either CRITICAL, ERROR, WARNING, INFO, DEBUG."
    },
    {
      "variable": "WEATHER_VERBOSE",
      "help": "Increase the default WARNING verbosity by one level for each additional repetition of the option."
    },
    {
      "variable": "WEATHER_QUIET",
      "help": "Decrease the default WARNING verbosity by one level for each additional repetition of the option."
    },
    {
      "variable": "WEATHER_TREE",
      "help": "Show the tree of nested subcommands and exit."
    },
    {
      "variable": "WEATHER_MAN",
      "help": "Read the command's manual page and exit."
    },
    {
      "variable": "WEATHER_HELP_FORMAT",
      "help": "Render the command in the given format and exit."
    },
    {
      "variable": "WEATHER_VERSION",
      "help": "Show the version and exit."
    },
    {
      "variable": "WEATHER_HELP",
      "help": "Show this message and exit."
    }
  ],
  "files": [
    "~/.config/weather/{*.toml,*.yaml,*.yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}"
  ],
  "exit_status": [
    {
      "code": "0",
      "meaning": "Success."
    },
    {
      "code": "1",
      "meaning": "A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation)."
    },
    {
      "code": "2",
      "meaning": "A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file."
    }
  ]
}

Markdown

The same command, in what a language model reads most comfortably:

$ weather --help-format markdown
# weather
Report the current temperature for a city.
## Synopsis
```shell-session
$ weather [OPTIONS] CITY

Description

Report the current temperature for a city.

Arguments

  • CITY: Name of the city to report on.

Options

  • --units [celsius|fahrenheit]: Temperature scale to display.

  • --time / --no-time: Measure and print elapsed execution time.

  • --config CONFIG_PATH: Location of the configuration file. Supports local path with glob patterns or remote URL.

  • --no-config: Ignore all configuration files and only use command line parameters and environment variables.

  • --validate-config FILE: Validate the configuration file and exit.

  • --export-config FORMAT: Export the configuration in the selected format to , then exit.

  • --accessible: Accessibility mode: disable colors and render tables in a borderless, screen-reader-friendly format.

  • --color[=auto|always|never]: Colorize the output. A bare –color is the same as –color=always.

  • --no-color: Disable colorization (alias of –color=never).

  • --progress / --no-progress: Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by –accessible.

  • --theme [auto|dark|dracula|light|manpage|monokai|nord|solarized_dark]: Color theme used for help screens.

  • --params: Show all CLI parameters, their provenance, defaults and value, then exit.

  • --table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]: Rendering style of tables.

  • --verbosity LEVEL: Either CRITICAL, ERROR, WARNING, INFO, DEBUG.

  • --verbose / -v: Increase the default WARNING verbosity by one level for each additional repetition of the option.

  • --quiet / -q: Decrease the default WARNING verbosity by one level for each additional repetition of the option.

  • --tree: Show the tree of nested subcommands and exit.

  • --man: Read the command’s manual page and exit.

  • --help-format [carapace|json|json-full|man|markdown|markdown-full]: Render the command in the given format and exit.

  • --version: Show the version and exit.

  • -h / --help: Show this message and exit.

Environment variables

  • WEATHER_UNITS: Temperature scale to display.

  • WEATHER_TIME: Measure and print elapsed execution time.

  • WEATHER_CONFIG: Location of the configuration file. Supports local path with glob patterns or remote URL.

  • WEATHER_VALIDATE_CONFIG: Validate the configuration file and exit.

  • WEATHER_EXPORT_CONFIG: Export the configuration in the selected format to , then exit.

  • WEATHER_ACCESSIBLE: Accessibility mode: disable colors and render tables in a borderless, screen-reader-friendly format.

  • WEATHER_COLOR: Colorize the output. A bare –color is the same as –color=always.

  • WEATHER_NO_COLOR: Disable colorization (alias of –color=never).

  • WEATHER_PROGRESS: Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by –accessible.

  • WEATHER_THEME: Color theme used for help screens.

  • WEATHER_PARAMS: Show all CLI parameters, their provenance, defaults and value, then exit.

  • WEATHER_TABLE_FORMAT: Rendering style of tables.

  • WEATHER_VERBOSITY: Either CRITICAL, ERROR, WARNING, INFO, DEBUG.

  • WEATHER_VERBOSE: Increase the default WARNING verbosity by one level for each additional repetition of the option.

  • WEATHER_QUIET: Decrease the default WARNING verbosity by one level for each additional repetition of the option.

  • WEATHER_TREE: Show the tree of nested subcommands and exit.

  • WEATHER_MAN: Read the command’s manual page and exit.

  • WEATHER_HELP_FORMAT: Render the command in the given format and exit.

  • WEATHER_VERSION: Show the version and exit.

  • WEATHER_HELP: Show this message and exit.

Files

  • ~/.config/weather/{*.toml,*.yaml,*.yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}

Exit status

  • 0: Success.

  • 1: A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation).

  • 2: A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file.


The format list

Format

What it renders

carapace

A Carapace completion spec (YAML), which doubles as a command-and-flag tree.

json

This command as a JSON object, its direct subcommands listed by name.

json-full

Every command of the tree, under a commands array.

man

This command as a man page: the roff source --man typesets to read.

markdown

This command as a Markdown document, one section per topic.

markdown-full

Every command of the tree as one Markdown document.

Note

The plain and -full variants differ in how much they hand over at once. A plain render describes one command and names its children, so a reader descends one level at a time rather than pulling a whole tree into a context window to answer a question about one leaf. The -full variants are for the opposite job: generating documentation, or diffing a CLI’s whole surface between two releases.

Whatever --color says, these renderings carry no ANSI codes: they are meant to be piped into a parser, which has no use for escape sequences. --help remains the colorized human view.

Installing the artifacts

Two of these renderings are installed rather than read: a man page under a man directory, a Carapace spec under Carapace’s. For those, the wrapper takes a destination instead of printing to stdout:

$ click-extra wrap --help-format man --install -- flask
/home/me/.local/share/man/man1/flask.1
/home/me/.local/share/man/man1/flask-run.1
$ click-extra wrap --help-format carapace --install -- flask
/home/me/.config/carapace/specs/flask.yaml

--install means the same thing for both: put this where its consumer looks for it, honoring XDG_DATA_HOME and XDG_CONFIG_HOME. --output-dir DIR writes it somewhere else instead. Both are refused for the other formats, which are documents nothing goes looking for: a shell redirection is the whole story there.

Any Click CLI

A CLI that has never heard of Click Extra gets the same treatment through the wrapper: it is loaded and walked from the outside, so an agent is not limited to the tools whose authors thought about it.

$ click-extra wrap --help-format json -- flask run
$ click-extra wrap --help-format markdown -- flask

The parameter inventory comes out the same way, in any structured format, values keeping their native types:

$ click-extra wrap --params --table-format json -- flask run
[
  {
    "ID": "run.cert",
    "Spec.": "--cert PATH",
    "Class": "click.core.Option",
    "Param type": "flask.cli.CertParamType",
    "Python type": "str",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_CERT"
    ],
    "Default": null,
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.debug",
    "Spec.": "--debug / --no-debug",
    "Class": "click.core.Option",
    "Param type": "click.types.BoolParamType",
    "Python type": "bool",
    "Hidden": false,
    "Exposed": false,
    "Env. vars.": [
      "FLASK_RUN_DEBUG"
    ],
    "Default": false,
    "Is flag": true,
    "Flag value": true,
    "Is bool flag": true,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.debugger",
    "Spec.": "--debugger / --no-debugger",
    "Class": "click.core.Option",
    "Param type": "click.types.BoolParamType",
    "Python type": "bool",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_DEBUGGER"
    ],
    "Default": null,
    "Is flag": true,
    "Flag value": true,
    "Is bool flag": true,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.exclude_patterns",
    "Spec.": "--exclude-patterns PATH",
    "Class": "click.core.Option",
    "Param type": "flask.cli.SeparatedPathType",
    "Python type": "str",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_EXCLUDE_PATTERNS"
    ],
    "Default": null,
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.extra_files",
    "Spec.": "--extra-files PATH",
    "Class": "click.core.Option",
    "Param type": "flask.cli.SeparatedPathType",
    "Python type": "str",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_EXTRA_FILES"
    ],
    "Default": null,
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.help",
    "Spec.": "--help",
    "Class": "click.core.Option",
    "Param type": "click.types.BoolParamType",
    "Python type": "bool",
    "Hidden": false,
    "Exposed": false,
    "Env. vars.": [
      "FLASK_RUN_HELP"
    ],
    "Default": false,
    "Is flag": true,
    "Flag value": true,
    "Is bool flag": true,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.host",
    "Spec.": "-h, --host TEXT",
    "Class": "click.core.Option",
    "Param type": "click.types.StringParamType",
    "Python type": "str",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_HOST"
    ],
    "Default": "127.0.0.1",
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.key",
    "Spec.": "--key FILE",
    "Class": "click.core.Option",
    "Param type": "click.types.Path",
    "Python type": "str",
    "Hidden": false,
    "Exposed": false,
    "Env. vars.": [
      "FLASK_RUN_KEY"
    ],
    "Default": null,
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.port",
    "Spec.": "-p, --port INTEGER",
    "Class": "click.core.Option",
    "Param type": "click.types.IntParamType",
    "Python type": "int",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_PORT"
    ],
    "Default": 5000,
    "Is flag": false,
    "Flag value": null,
    "Is bool flag": false,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.reload",
    "Spec.": "--reload / --no-reload",
    "Class": "click.core.Option",
    "Param type": "click.types.BoolParamType",
    "Python type": "bool",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_RELOAD"
    ],
    "Default": null,
    "Is flag": true,
    "Flag value": true,
    "Is bool flag": true,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  },
  {
    "ID": "run.with_threads",
    "Spec.": "--with-threads / --without-threads",
    "Class": "click.core.Option",
    "Param type": "click.types.BoolParamType",
    "Python type": "bool",
    "Hidden": false,
    "Exposed": true,
    "Env. vars.": [
      "FLASK_RUN_WITH_THREADS"
    ],
    "Default": true,
    "Is flag": true,
    "Flag value": true,
    "Is bool flag": true,
    "Multiple": false,
    "Nargs": 1,
    "Prompt": null,
    "Confirmation prompt": false,
    "Value": null,
    "Source": null
  }
]

Pair it with --columns to hand a consumer only the fields it reads:

$ click-extra wrap --params --table-format json --columns id,spec,envvars,default -- flask routes
[
  {
    "ID": "routes.all_methods",
    "Spec.": "--all-methods",
    "Env. vars.": [
      "FLASK_ROUTES_ALL_METHODS"
    ],
    "Default": false
  },
  {
    "ID": "routes.help",
    "Spec.": "--help",
    "Env. vars.": [
      "FLASK_ROUTES_HELP"
    ],
    "Default": false
  },
  {
    "ID": "routes.sort",
    "Spec.": "-s, --sort [endpoint|methods|domain|rule|match]",
    "Env. vars.": [
      "FLASK_ROUTES_SORT"
    ],
    "Default": "endpoint"
  }
]

Where --params describes the parameters, --help-format describes the command itself: its usage line, description, option groups, subcommands and examples. The target cooperates with neither, and needs to know nothing about Click Extra:

$ click-extra wrap --help-format json -- flask run
{
  "name": "flask run",
  "short_help": "Run a development server.",
  "version": null,
  "synopsis": "flask run [OPTIONS]",
  "description": "Run a local development server.\n\nThis server is for development purposes only. It does not provide\nthe stability, security, or performance of production WSGI servers.\n\nThe reloader and debugger are enabled by default with the '--debug'\noption.",
  "arguments": [],
  "option_groups": [
    {
      "title": null,
      "help": null,
      "options": [
        {
          "names": [
            "--debug",
            "--no-debug"
          ],
          "spec": "--debug / --no-debug",
          "metavar": null,
          "help": "Set debug mode.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--host",
            "-h"
          ],
          "spec": "--host / -h TEXT",
          "metavar": "TEXT",
          "help": "The interface to bind to.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--port",
            "-p"
          ],
          "spec": "--port / -p INTEGER",
          "metavar": "INTEGER",
          "help": "The port to bind to.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--cert"
          ],
          "spec": "--cert PATH",
          "metavar": "PATH",
          "help": "Specify a certificate file to use HTTPS.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--key"
          ],
          "spec": "--key FILE",
          "metavar": "FILE",
          "help": "The key file to use when specifying a certificate.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--reload",
            "--no-reload"
          ],
          "spec": "--reload / --no-reload",
          "metavar": null,
          "help": "Enable or disable the reloader. By default the reloader is active if debug is enabled.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--debugger",
            "--no-debugger"
          ],
          "spec": "--debugger / --no-debugger",
          "metavar": null,
          "help": "Enable or disable the debugger. By default the debugger is active if debug is enabled.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--with-threads",
            "--without-threads"
          ],
          "spec": "--with-threads / --without-threads",
          "metavar": null,
          "help": "Enable or disable multithreading.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--extra-files"
          ],
          "spec": "--extra-files PATH",
          "metavar": "PATH",
          "help": "Extra files that trigger a reload on change. Multiple paths are separated by ':'.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--exclude-patterns"
          ],
          "spec": "--exclude-patterns PATH",
          "metavar": "PATH",
          "help": "Files matching these fnmatch patterns will not trigger a reload on change. Multiple patterns are separated by ':'.",
          "required": false,
          "optional_value": false
        },
        {
          "names": [
            "--help"
          ],
          "spec": "--help",
          "metavar": null,
          "help": "Show this message and exit.",
          "required": false,
          "optional_value": false
        }
      ]
    }
  ],
  "subcommands": [],
  "examples": [],
  "environment": [],
  "files": [],
  "exit_status": [
    {
      "code": "0",
      "meaning": "Success."
    },
    {
      "code": "1",
      "meaning": "A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation)."
    },
    {
      "code": "2",
      "meaning": "A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file."
    }
  ]
}

Feeding a CLI to an agent

Three things are worth knowing before wiring any of this into a tool or a model.

Hand a model Markdown, not JSON. Both carry the same content, and JSON is the right answer for code that indexes fields. But a model reads prose better than it reads a nested object, and pays fewer tokens for it: no quoting, no punctuation scaffolding, and headings it already knows how to skim.

Descend, do not dump. The plain formats name a command’s children without expanding them, so an agent answering a question about one leaf spends its context on that leaf rather than on a tree it will not read. Reach for -full when the whole surface is the point (generating documentation, diffing two releases), not by default. On a large CLI the difference is the whole budget.

Two lookalikes that answer different questions. --help-format json describes the interface: what the command is, what it accepts, what it documents. --params describes the state of one invocation: every parameter’s resolved value and where it came from, be that a flag, an environment variable, a configuration file or a default. An agent picking flags wants the first. An agent debugging why a run behaved a certain way wants the second.

Neither requires the target to cooperate: click-extra wrap extracts both from a CLI that knows nothing about Click Extra.

click_extra.command_doc API

        classDiagram
  ExtraOption <|-- HelpFormatOption
  ExtraOption <|-- ManOption
    

Extract a Click command into a structured document and render it.

extract_command_doc() walks a command (and, through iter_command_contexts(), its whole tree) into a CommandDoc: one extraction carrying the man-pages(7) sections documented in Man-page (NAME, SYNOPSIS, DESCRIPTION, OPTIONS, COMMANDS, ENVIRONMENT, FILES and EXIT STATUS). The model then renders to any of the HELP_FORMATS backends: roff (CommandDoc.to_roff()), Markdown (CommandDoc.to_markdown()) and JSON (CommandDoc.to_dict() / CommandDoc.to_json()), with the Carapace completion spec delegated to click_extra.carapace.

The roff backend is Click Extra’s answer to the unmaintained click-man package. It improves on it by:

  • working on a command object via click.Command.make_context(), so it needs no console_scripts entry point;

  • discovering subcommands dynamically through click.Group.list_commands() / click.Group.get_command() with a live context;

  • honoring Click’s \b no-rewrap marker (rendered as roff .nf / .fi);

  • rendering boolean flags (--foo / --no-foo) and skipping hidden commands and options;

  • mirroring Cloup option groups as .SS subsections of OPTIONS (ungrouped options fall under an Other options heading), matching the help screen;

  • emitting ENVIRONMENT (from auto-generated env vars), FILES (from the --config search pattern) and EXIT STATUS sections that click-man never grew.

Font selection follows the man typographic convention encoded by click_extra.theme.LITERAL_STYLES / REPLACEABLE_STYLES: literal tokens (command and option names) render bold (\fB), replaceable tokens (metavars, operands) render italic (\fI).

click_extra.command_doc.INLINE_LITERAL_RE = re.compile('``([^`]+?)``')

Match a reST inline literal ("…``”``) in a docstring.

Click stores docstrings verbatim, so any reST markup the author used to render code-like tokens in HTML docs leaks into Command.help / Command.short_help. The roff and HTML man-page paths translate these matches into the bold/literal markers their renderers understand; the Sphinx index directive translates them into nodes.literal.

click_extra.command_doc.iter_inline_literals(text)[source]

Walk text and yield (segment, is_literal) pairs.

Split on INLINE_LITERAL_RE so the consumer can apply different rendering to the literal segments (bold for roff, a literal node for docutils) without re-parsing the regex.

Return type:

Iterator[tuple[str, bool]]

click_extra.command_doc.CLICK_EXTRA_URL = 'https://github.com/kdeldycke/click-extra'

Click Extra’s home page, stamped into the provenance comment of every generated man page so a reader of the raw roff knows where it came from.

click_extra.command_doc.MAN_SECTION = '1'

Default man page section. Section 1 is for executable programs and shell commands, which is what a Click CLI is.

click_extra.command_doc.DEFAULT_EXIT_STATUS: tuple[tuple[str, str], ...] = (('0', 'Success.'), ('1', 'A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation).'), ('2', 'A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file.'))

Conventional exit codes shared by every Click Extra CLI.

Mirrors the EXIT STATUS table in Man-page. Click returns 2 for usage errors (UsageError), 1 for aborts, and 0 on success.

click_extra.command_doc.normalize_examples(examples)[source]

Validate and freeze a command’s examples into (description, command) pairs.

Accepts any sequence of two-item sequences, so a list of tuples and a list of lists (what a configuration file or a JSON payload would produce) are both fine. None and an empty sequence both yield an empty tuple.

Raises:

TypeError – when an entry is not a pair of strings, naming the offending entry. Raised at command construction, so a malformed example surfaces on import rather than on the first --help a user runs.

Return type:

tuple[tuple[str, str], ...]

class click_extra.command_doc.DocOptionItem(names, metavar, help, required, optional_value=False)[source]

Bases: object

A single OPTIONS entry, extracted from a Click option.

names: tuple[str, ...]

All literal spellings: primary opts followed by secondary_opts (so --foo / --no-foo boolean flags render both).

metavar: str | None

The rendered metavar, or None when the option takes no value (boolean flags and counters).

help: str | None

The option’s help text, possibly carrying a \b no-rewrap marker.

required: bool

Whether the option is mandatory.

optional_value: bool = False

Whether the option’s value is optional (a bare flag is allowed). Rendered as the attached [=METAVAR] form instead of a space-separated metavar.

to_roff()[source]

Render this option as a roff tagged paragraph (.TP).

Return type:

list[str]

property spec: str

The option’s spelling and value placeholder, as one plain string.

to_markdown()[source]

Render this option as a Markdown list item.

A \b no-rewrap region in the help becomes a fenced block indented under the item, rather than being folded into the sentence: the author aligned it on purpose, and a list item can carry a block as well as a paragraph can.

Return type:

list[str]

to_dict()[source]

Render this option as a JSON-serializable mapping.

Return type:

dict[str, Any]

class click_extra.command_doc.DocOptionGroup(options, title=None, help=None)[source]

Bases: object

A titled cluster of OPTIONS entries, mirroring a Cloup option group.

A plain Click command, or a Cloup command with no explicit @option_group, yields a single group with title=None: it renders as a flat OPTIONS list with no .SS subsection heading, identical to a man page that never grouped its options.

options: tuple[DocOptionItem, ...]

The option entries in this group.

title: str | None = None

The subsection heading, rendered as a roff .SS. None for the implicit single group of an ungrouped command (no heading emitted).

help: str | None = None

Optional group description, rendered as prose under the heading.

to_roff()[source]

Render an optional .SS heading, group help, then the options.

Return type:

list[str]

to_markdown(level=3)[source]

Render an optional heading, group help, then the options.

Return type:

list[str]

to_dict()[source]

Render this group as a JSON-serializable mapping.

Return type:

dict[str, Any]

class click_extra.command_doc.CommandDoc(name, short_help='', section='1', synopsis_pieces=(), description='', operands=(), option_groups=(), subcommands=(), environment=(), files=(), exit_status=(('0', 'Success.'), ('1', 'A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation).'), ('2', 'A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file.')), examples=(), version=None, date='', manual=None, authors=None, copyright=None)[source]

Bases: object

A whole man page in structured form, ready to render to roff.

One CommandDoc maps to one command (or subcommand). Its fields are the man-pages(7) sections, in the order Man-page documents them. Build it with extract_command_doc() and serialize with to_roff().

name: str

Full command path, space-joined (like weather forecast).

short_help: str = ''

One-line description for the NAME section.

section: str = '1'

Man section number.

synopsis_pieces: tuple[str, ...] = ()

Usage metavars after the command name ([OPTIONS], CITY, …).

description: str = ''

The command’s full help text / docstring for the DESCRIPTION section.

operands: tuple[tuple[str, str], ...] = ()

Positional arguments as (metavar, help) pairs.

option_groups: tuple[DocOptionGroup, ...] = ()

The OPTIONS entries, partitioned into one or more groups. A command without explicit option groups carries a single untitled group.

subcommands: tuple[tuple[str, str], ...] = ()

For groups: (name, short_help) pairs for the COMMANDS section.

environment: tuple[tuple[str, str], ...] = ()

ENVIRONMENT entries as (variable_name, help) pairs.

files: tuple[str, ...] = ()

FILES entries (configuration search patterns).

exit_status: tuple[tuple[str, str], ...] = (('0', 'Success.'), ('1', 'A runtime error, or an aborted prompt (Ctrl-C, a declined confirmation).'), ('2', 'A usage error: unknown option, invalid value, missing operand, or an unparsable configuration file.'))

EXIT STATUS entries as (code, meaning) pairs.

examples: tuple[tuple[str, str], ...] = ()

EXAMPLES entries as (description, command_line) pairs.

Collected from the command’s own examples attribute (see click_extra.commands.Command.examples). Empty for a command that declares none, in which case every backend omits the section entirely.

version: str | None = None

Version string for the .TH header.

date: str = ''

Date for the .TH header (YYYY-MM-DD).

manual: str | None = None

Manual name for the .TH header (the centered footer title).

authors: str | None = None

AUTHORS section content, or None to omit the section.

copyright: str | None = None

COPYRIGHT section content, or None to omit the section.

property title: str

The .TH page title: the command path, hyphen-joined and upper-cased.

to_roff()[source]

Render the full man page as a roff/troff string.

Return type:

str

to_markdown()[source]

Render the whole document as Markdown.

Same sections as to_roff(), in the same order, minus the roff .TH header, whose date, section number and manual name describe a man page rather than the command. The version survives, as a line under the title.

Return type:

str

to_dict()[source]

Render the whole document as a JSON-serializable mapping.

Subcommands are listed by name and one-line description only, never recursively: a consumer walking a deep tree asks for the child it cares about instead of paying for the whole tree at once. render_help() exposes the recursive variant separately, for the consumers that do want everything.

Return type:

dict[str, Any]

to_json(indent=2)[source]

Serialize to_dict() to a JSON string.

Return type:

str

click_extra.command_doc.extract_command_doc(command, ctx, *, version=None, date=None, manual=None, authors=None, copyright=None)[source]

Build a CommandDoc from a Click command and its context.

The context must have been created for command (for example via click.Command.make_context() with resilient_parsing=True), so that auto-generated environment-variable prefixes resolve correctly.

Return type:

CommandDoc

click_extra.command_doc.iter_command_contexts(command, prog_name=None, _parent=None, _path=())[source]

Walk a command tree, yielding (path, command, context) for each visible command.

Subcommands are discovered dynamically (click.Group.list_commands() / get_command()), so dynamically-registered commands are included. Hidden commands are skipped. Each context is built with resilient_parsing=True to avoid triggering required-argument errors, prompts, or eager-option side effects.

Return type:

Iterator[tuple[tuple[str, ...], Command, Context]]

click_extra.command_doc.render_manpage(command, prog_name=None, ctx=None, **overrides)[source]

Render a single command’s man page as a roff string.

Reuses ctx when given (like the live invocation context), otherwise builds a throwaway one with resilient_parsing=True. Keyword overrides (version, date, manual, authors, copyright) are passed through to extract_command_doc().

Return type:

str

click_extra.command_doc.render_manpages(command, prog_name=None, **overrides)[source]

Render the whole command tree, one man page per (sub)command.

Returns an ordered mapping of {filename: roff} where each filename is the command path joined by hyphens plus the section suffix (like weather-forecast.1).

Return type:

dict[str, str]

click_extra.command_doc.write_manpages(command, target_dir, prog_name=None, **overrides)[source]

Render the command tree and write each man page into target_dir.

Creates target_dir if missing. Returns the list of written paths.

Return type:

list[Path]

click_extra.command_doc.install_manpages(command, prog_name=None, **overrides)[source]

Write the command tree’s man pages where man can find them.

Targets $XDG_DATA_HOME/man/man1 when that variable is set, else MAN_INSTALL_DIR. Returns the written paths.

The environment is read here rather than at import time, so a caller that sets XDG_DATA_HOME for one invocation (a test, a packaging script staging into a build root) is honored. This mirrors install_carapace_spec(), whose spec directory resolves the same way.

Return type:

list[Path]

click_extra.command_doc.HELP_FORMATS: dict[str, str] = {'carapace': 'Carapace completion spec (YAML). Doubles as a command-and-flag tree, and is the shape `carapace` itself consumes. Needs the `yaml` extra.', 'json': 'This command as a JSON object: usage, description, arguments, options grouped as the help screen groups them, environment variables, files, exit codes, and its direct subcommands by name.', 'json-full': 'Every command of the tree as JSON, under a `commands` array, each entry in the `json` shape.', 'man': 'This command as a man page: the roff source a packager installs, which `--man` typesets for reading.', 'markdown': 'This command as a Markdown document, one section per topic.', 'markdown-full': 'Every command of the tree as one Markdown document, in tree order.'}

The formats render_help() renders, mapped to their one-line description.

Ordered alphabetically, which is also the order --help-format advertises them in. Adding a format is an entry here plus a branch in render_help(): no new flag, no wider help screen. See Man-page for what each one is good for.

Note

The distinction the plain and -full variants draw is progressive disclosure. A plain render describes one command and names its children, so a reader (a tool or an agent, typically) descends one level at a time instead of pulling a whole tree into a context window to answer a question about one leaf. The -full variants exist for the opposite job: generating documentation, or diffing a CLI’s whole surface between two releases.

click_extra.command_doc.INSTALLABLE_FORMATS: frozenset[str] = frozenset({'carapace', 'man'})

The formats with a canonical place on disk their consumer reads them from.

A man page under a man directory, a Carapace spec under Carapace’s. These are the two renderings that are installed rather than read, which is what lets click-extra wrap offer them a destination (--output-dir, --install) and refuse one to the others. A JSON or Markdown document has no such place: nothing goes looking for it, so stdout and a shell redirection are the whole story.

click_extra.command_doc.render_help(command, help_format, prog_name=None, ctx=None, **overrides)[source]

Render command in one of the HELP_FORMATS.

Reuses ctx when given (like the live invocation context), otherwise builds a throwaway one with resilient_parsing=True, exactly like render_manpage(). Keyword overrides are passed through to extract_command_doc(), and ignored by the carapace format, which carries no version or authorship of its own.

Raises:

ValueError – on an unknown format, listing the known ones.

Return type:

str

click_extra.command_doc.MAN_FORMATTERS: tuple[tuple[str, ...], ...] = (('groff', '-man', '-Tutf8', '-rLL={width}n'), ('mandoc', '-Tutf8', '-Owidth={width}'))

Commands able to typeset roff into readable terminal text, best first.

Each entry is an argv template read on stdin, with :width: filled from the terminal. groff is the GNU implementation found nearly everywhere a man page is; mandoc covers the BSDs and Alpine, which ship it instead.

Note

The man binary is deliberately not in this list, even though it is the tool being imitated. Reading roff from stdin is where the implementations diverge: GNU man takes -l -, while the BSD one wants a real file path. Driving the typesetter directly sidesteps a portability problem that buys nothing, since paging is handled here anyway.

click_extra.command_doc.MAN_INSTALL_DIR: Path = PosixPath('/home/runner/.local/share/man/man1')

Where --install writes man pages: the user’s own section-1 directory.

The default of the XDG base directory spec, which install_manpages() overrides from XDG_DATA_HOME when that is set. Some systems do not carry this path in their MANPATH, in which case the pages land correctly but man has to be told where to look.

click_extra.command_doc.format_manpage(roff, width=None)[source]

Typeset roff into readable terminal text, or None if nothing can.

Tries each entry of MAN_FORMATTERS in turn and returns the output of the first that succeeds. Returns None when none of them is installed, which the caller is expected to degrade on rather than fail: a CLI that cannot find a typesetter is a CLI running somewhere that never had man pages to begin with (Windows, a slim container), and that is no reason for --man to error.

Parameters:
  • roff (str) – the man page source, as CommandDoc.to_roff() renders it.

  • width (int | None) – line length in columns. Defaults to the terminal’s own, so the result matches what man would have produced in the same window.

Return type:

str | None

click_extra.command_doc.OVERSTRIKE_RE = re.compile('.\\x08')

Match the character-backspace pairs a roff typesetter emits for emphasis.

A bold N is written N\x08N and an underlined one _\x08N, a convention inherited from line printers that a pager still renders as bold and underline today. Dropping the pair’s first half leaves the plain character.

click_extra.command_doc.read_manpage(command, ctx=None)[source]

Typeset a command’s manual and send it to the pager.

The reading counterpart of --help-format man, which emits the roff source a packager installs. Falls back to printing that source, with a warning naming what to install, when no typesetter is available: something on screen beats an error, and the source still carries every word of the manual.

Under --accessible the emphasis is stripped and the pager bypassed (echo_via_pager() streams instead). Both matter to the same reader: a pager is a cursor-driven takeover, and overstrike is worse than the ANSI codes accessible mode already removes, since a screen reader voices N\x08NA\x08AM\x08ME\x08E rather than skipping it.

Return type:

None

class click_extra.command_doc.ManOption(param_decls=None, is_flag=True, expose_value=False, is_eager=True, help="Read the command's manual page and exit.", **kwargs)[source]

Bases: ExtraOption

A pre-configured --man flag that typesets the command’s manual, pages it, and exits.

Eager and value-less, like ShowParamsOption. Part of the default option set injected by default_params(), so every @command and @group exposes it. Use @man_option to add it to a plain Click CLI.

Note

The flag is named --man, not --show-man or --man-page.

In the POSIX, GNU and BSD traditions a program does not emit its own man page through a flag: the page is a separate file read with man <prog>, either hand-written (BSD mdoc) or generated at build time from --help output (GNU help2man). Click Extra already covers that build-time path with write_manpages(), its help2man equivalent.

The one ecosystem that exposes a runtime flag is Perl’s Pod::Usage, whose convention is --help for the brief usage and bare --man for the full manual. --man also lines up with the neighbouring --help and --version informational flags, which use bare nouns with no show- prefix. --show-man and --man-page have no precedent outside Click Extra.

Note

That Perl convention is about reading a manual, and this flag used to print roff source instead, which nobody reads: it was a build artifact wearing a reader’s name. It now typesets the page and sends it to the pager, the way man itself does, so the flag does what its tradition says.

The source did not go away, it moved to where a build step looks for it: --help-format man, beside every other artifact this module renders. The two are one question apart. Do you want to read the manual, or to ship it?

print_man(ctx, param, value)[source]

Typeset the invoked command’s manual, page it, then exit.

Return type:

None

class click_extra.command_doc.HelpFormatOption(param_decls=None, expose_value=False, is_eager=True, help='Render the command in the given format and exit.', **kwargs)[source]

Bases: ExtraOption

A pre-configured --help-format option printing the command in one of the HELP_FORMATS and exiting.

Eager and value-taking, unlike its --man neighbour, which is the same renderer reached through a bare flag: --man is exactly --help-format roff, kept because a runtime manual flag has its own tradition (see ManOption).

Note

One option carrying a format, rather than one flag per format. A CLI’s option list is the most expensive real estate in its help screen, and every reader pays for it whether or not they will ever export anything: a family of --help-json, --help-markdown and --help-carapace flags would widen the label column of every screen, forever, one line per format anyone ever adds. Here a new format costs an entry in HELP_FORMATS and nothing on screen.

Note

The rendered output is deliberately colorless whatever --color says. Every format here is meant to be piped into something (a file, a parser, a model), and ANSI escapes in a JSON string or a Markdown fence are noise to all of them. --help remains the colorized human view.

print_help_format(ctx, param, value)[source]

Render the invoked command in the requested format, then exit.

Return type:

None