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? |
|
this page |
What are the parameters, their types, and where did each value come from? |
||
What is the resolved configuration, as a file I can edit and replay? |
||
What subcommands exist? |
|
|
How do I complete this in a shell? |
|
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 |
|---|---|
|
A Carapace completion spec (YAML), which doubles as a command-and-flag tree. |
|
This command as a JSON object, its direct subcommands listed by name. |
|
Every command of the tree, under a |
|
This command as a man page: the roff source |
|
This command as a Markdown document, one section per topic. |
|
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 noconsole_scriptsentry point;discovering subcommands dynamically through
click.Group.list_commands()/click.Group.get_command()with a live context;honoring Click’s
\bno-rewrap marker (rendered as roff.nf/.fi);rendering boolean flags (
--foo/--no-foo) and skipping hidden commands and options;mirroring Cloup option groups as
.SSsubsections of OPTIONS (ungrouped options fall under anOther optionsheading), matching the help screen;emitting ENVIRONMENT (from auto-generated env vars), FILES (from the
--configsearch 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 intonodes.literal.
- click_extra.command_doc.iter_inline_literals(text)[source]
Walk
textand yield(segment, is_literal)pairs.Split on
INLINE_LITERAL_REso the consumer can apply different rendering to the literal segments (bold for roff, aliteralnode for docutils) without re-parsing the regex.
- 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
2for usage errors (UsageError),1for aborts, and0on success.
- click_extra.command_doc.normalize_examples(examples)[source]
Validate and freeze a command’s
examplesinto(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.
Noneand an empty sequence both yield an empty tuple.
- class click_extra.command_doc.DocOptionItem(names, metavar, help, required, optional_value=False)[source]
Bases:
objectA single OPTIONS entry, extracted from a Click option.
- names: tuple[str, ...]
All literal spellings: primary
optsfollowed bysecondary_opts(so--foo/--no-fooboolean flags render both).
- metavar: str | None
The rendered metavar, or
Nonewhen the option takes no value (boolean flags and counters).
- 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.
- 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
\bno-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.
- class click_extra.command_doc.DocOptionGroup(options, title=None, help=None)[source]
Bases:
objectA 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 withtitle=None: it renders as a flat OPTIONS list with no.SSsubsection 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.Nonefor the implicit single group of an ungrouped command (no heading emitted).
- to_roff()[source]
Render an optional
.SSheading, group help, then the options.
- to_markdown(level=3)[source]
Render an optional heading, group help, then the options.
- 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:
objectA whole man page in structured form, ready to render to roff.
One
CommandDocmaps to one command (or subcommand). Its fields are the man-pages(7) sections, in the order Man-page documents them. Build it withextract_command_doc()and serialize withto_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.
- description: str = ''
The command’s full help text / docstring for the DESCRIPTION section.
- 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.
- 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
examplesattribute (seeclick_extra.commands.Command.examples). Empty for a command that declares none, in which case every backend omits the section entirely.
- date: str = ''
Date for the
.THheader (YYYY-MM-DD).
- property title: str
The
.THpage title: the command path, hyphen-joined and upper-cased.
- to_markdown()[source]
Render the whole document as Markdown.
Same sections as
to_roff(), in the same order, minus the roff.THheader, 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:
- 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.
- click_extra.command_doc.extract_command_doc(command, ctx, *, version=None, date=None, manual=None, authors=None, copyright=None)[source]
Build a
CommandDocfrom a Click command and its context.The context must have been created for
command(for example viaclick.Command.make_context()withresilient_parsing=True), so that auto-generated environment-variable prefixes resolve correctly.- Return type:
- 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 withresilient_parsing=Trueto avoid triggering required-argument errors, prompts, or eager-option side effects.
- 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
ctxwhen given (like the live invocation context), otherwise builds a throwaway one withresilient_parsing=True. Keyword overrides (version,date,manual,authors,copyright) are passed through toextract_command_doc().- Return type:
- 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 (likeweather-forecast.1).
- 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_dirif missing. Returns the list of written paths.
- click_extra.command_doc.install_manpages(command, prog_name=None, **overrides)[source]
Write the command tree’s man pages where
mancan find them.Targets
$XDG_DATA_HOME/man/man1when that variable is set, elseMAN_INSTALL_DIR. Returns the written paths.The environment is read here rather than at import time, so a caller that sets
XDG_DATA_HOMEfor one invocation (a test, a packaging script staging into a build root) is honored. This mirrorsinstall_carapace_spec(), whose spec directory resolves the same way.
- 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-formatadvertises them in. Adding a format is an entry here plus a branch inrender_help(): no new flag, no wider help screen. See Man-page for what each one is good for.Note
The distinction the plain and
-fullvariants 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-fullvariants 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
mandirectory, a Carapace spec under Carapace’s. These are the two renderings that are installed rather than read, which is what letsclick-extra wrapoffer 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
ctxwhen given (like the live invocation context), otherwise builds a throwaway one withresilient_parsing=True, exactly likerender_manpage(). Keyword overrides are passed through toextract_command_doc(), and ignored by thecarapaceformat, which carries no version or authorship of its own.- Raises:
ValueError – on an unknown format, listing the known ones.
- Return type:
- 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.groffis the GNU implementation found nearly everywhere a man page is;mandoccovers the BSDs and Alpine, which ship it instead.Note
The
manbinary is deliberately not in this list, even though it is the tool being imitated. Reading roff from stdin is where the implementations diverge: GNUmantakes-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
--installwrites man pages: the user’s own section-1 directory.The default of the XDG base directory spec, which
install_manpages()overrides fromXDG_DATA_HOMEwhen that is set. Some systems do not carry this path in theirMANPATH, in which case the pages land correctly butmanhas to be told where to look.
- click_extra.command_doc.format_manpage(roff, width=None)[source]
Typeset roff into readable terminal text, or
Noneif nothing can.Tries each entry of
MAN_FORMATTERSin turn and returns the output of the first that succeeds. ReturnsNonewhen 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--manto error.
- click_extra.command_doc.OVERSTRIKE_RE = re.compile('.\\x08')
Match the character-backspace pairs a roff typesetter emits for emphasis.
A bold
Nis writtenN\x08Nand 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
--accessiblethe 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 voicesN\x08NA\x08AM\x08ME\x08Erather than skipping it.- Return type:
- 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:
ExtraOptionA pre-configured
--manflag that typesets the command’s manual, pages it, and exits.Eager and value-less, like
ShowParamsOption. Part of the default option set injected bydefault_params(), so every@commandand@groupexposes it. Use@man_optionto add it to a plain Click CLI.Note
The flag is named
--man, not--show-manor--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 (BSDmdoc) or generated at build time from--helpoutput (GNUhelp2man). Click Extra already covers that build-time path withwrite_manpages(), itshelp2manequivalent.The one ecosystem that exposes a runtime flag is Perl’s
Pod::Usage, whose convention is--helpfor the brief usage and bare--manfor the full manual.--manalso lines up with the neighbouring--helpand--versioninformational flags, which use bare nouns with noshow-prefix.--show-manand--man-pagehave 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
manitself 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?
- 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:
ExtraOptionA pre-configured
--help-formatoption printing the command in one of theHELP_FORMATSand exiting.Eager and value-taking, unlike its
--manneighbour, which is the same renderer reached through a bare flag:--manis exactly--help-format roff, kept because a runtime manual flag has its own tradition (seeManOption).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-markdownand--help-carapaceflags would widen the label column of every screen, forever, one line per format anyone ever adds. Here a new format costs an entry inHELP_FORMATSand nothing on screen.Note
The rendered output is deliberately colorless whatever
--colorsays. 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.--helpremains the colorized human view.