Skip to content

Add UAPI.18: CLI introspection - #223

Draft
behrmann wants to merge 51 commits into
uapi-group:mainfrom
behrmann:cli-introspection
Draft

behrmann wants to merge 51 commits into
uapi-group:mainfrom
behrmann:cli-introspection

Conversation

@behrmann

Copy link
Copy Markdown
Member

This is a draft that @keszybz based his work in systemd/systemd#43152 on (with a few typos) fixed. A few commits have been put on top addressing a few things that where found during the review of that PR already and that were commented on behrmann@e7911dc#comments. It also adds what's currently pending in systemd/systemd#43430.

The idea is to have a JSON schema that describes the full output (or a superset of that) that one would get from a binary via --help in a structured way. The idea is that this would allow to automatically generate command line completion from this, which we have already done in mkosi, but would also allow other things, like checking for features of a command line program without resorting to brittle parsing of help output.

This is still very much work in progress.

Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
"version": "myversion",
"features": ["feature1", "feature2", …]
"abstract": ["paragraph1", "paragraph2", …],
"postscript": ["paragraph1", "paragraph2", …],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should find a better name for "footer", as that indicates location where to print that, not the category of the text it contains.

Changed it to postscript, but I'm open to other things.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about epilog and prolog? The script naming seems quite confusing in this context

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "header" and "footer"? Sounds more down-to-earth.

Comment thread specs/cli_introspection.md
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md

@keszybz keszybz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using semantic breaks? I think it'd also make making review comment easier.

There should be a complete example, or maybe even more than one.

Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
Comment thread specs/cli_introspection.md Outdated
this option may not be called together with.

`values` is an array of *value objects* describing the values this option may
take. Value objects are described in a section below.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make clear that absence or null of this means that the syntax/dictionary of the values is left open

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a formulation regarding this. PTAL whether the intent "this is some arbitrary blob we don't say anything about" comes across.

@poettering

Copy link
Copy Markdown
Collaborator

verb objects are not defined yet, but i guess you know that

@poettering poettering changed the title CLI introspection Add UAPI.18: CLI introspection Aug 25, 2026
Comment thread specs/cli_introspection.md Outdated
Comment on lines +98 to +100
`verbs` are command objects that describe a program's verbs, also called
subcommands. Verbs may recursively define further verbs up to a maximum depth
that should not be larger than 16.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should verbs have their own features and version and project fields? Maybe say that those are not allowed except at the top level?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense for them to have their own, e.g. git just accepts everything as a verb that's git-foo and there are third-party projects hooking into that and providing git subcommands. In that sense I think it makes sense to have this, since a top-level command that uses such an extension mechanism could still generate a global CLI introspection by calling --introspect-cli (or similar) on all of its extensions.

@keszybz

keszybz commented Aug 26, 2026

Copy link
Copy Markdown
Member

verb objects are not defined yet, but i guess you know that

They are defined as recursive commands.

@behrmann

Copy link
Copy Markdown
Member Author

If this is really supposed to become a universal standard it seems like it must get a decent bit more flexible even for quite common scenarios.

That is true, I still have a few outstanding things that need typing up:

  • a type for option separators like --, which need an ability to dispatch to option parsing for other commands, which would be useful for wrapper commands that run another one. Completion for this is something that fish supports.
  • a way to refer back to existing options, e.g. systemctl will happily accept options after the verb and its arguments, which is currently not covered here, so a completion generator would not know what to do
  • number and separator of arguments for options
1. Currently there seems to be mention on how to support nested commands (like `git remote add` or `ip address show`).

Happy to clarify this if this is not obvious as it might be lost among the discussion here (and also in the PR by @keszybz that is open on this PR in my fork of the repo), but the schema is recursive, so a command can have commands as arguments, this is the intended mechanism to capture subcommands like git does them.

I think I'll try to to (a subset) of ip as another example, as that is a sufficiently complex one to stress test this.

2. There is no way to specify which flag applies to which command apart from general sections. This is also my biggest gripe with systemd's help output. Sure, I can look at `systemctl --help` but it does not tell me anything about which flags apply to `systemctl show`, or which commands repect `--output=json`.

Yeah, I'm in that boat as well. :) This is mainly a difference of taste, for systemd tooling all options live in a single namespace

And that is still excluding crazy shenanigans like GStreamer's gst-launch or ffmpeg

For gst-launch I assume you mean the pipeline descriptions? Yeah, no idea there, will have to look at this, but I think it's also fair to acknowledge that not every DSL may be captured.

Oh and on another note: Many argument-parsing libraries which provide tab completion support, work by allowing to invoke the command with the currently spelled out flags/options and passing in another special flag (or environment variable) and then returning the list of feasible options.

That is intended to be captured by the dynamic attribute for options, but maybe something similar for arguments would make sense as well.

@poettering

Copy link
Copy Markdown
Collaborator
  1. Currently there seems to be mention on how to support nested commands (like git remote add or ip address show).

so my understanding is that the ability to have commands objects as one type of arguments in command objects is what open this up. i.e. the command object "ip" has a command object called "address" as parameter, which has a command object "show" as parameter, and so on.

but honestly, we should play around with this, and see if we can actually model a semi-complex real-life project's cmdline that follows this style with this model. just because i think it works it doesn't mean it actually does ;-)

@poettering

Copy link
Copy Markdown
Collaborator
  • a way to refer back to existing options, e.g. systemctl will happily accept options after the verb and its arguments, which is currently not covered here, so a completion generator would not know what to do

this is not specific to systemd btw. it's a property of glibc getopt/getopt_long, which we just inherited (and which i think makes sense). stupidly, getopt/getop_long on bsd and musl don't do that. but a while back we stopped using libc-provided getopt_long and since then actually expose identical behaviour on glibc and musl.

@poettering

Copy link
Copy Markdown
Collaborator

For gst-launch I assume you mean the pipeline descriptions? Yeah, no idea there, will have to look at this, but I think it's also fair to acknowledge that not every DSL may be captured.

so i guess we could fundamentally model the gst-launch ! thing via a command object with a name of !. Except that we currently don't allow references to commands we already have defined, which we'd need here. maybe we should double down on the id thin, and then allow this to be used to reference already defined objects multiple times.

ototh maybe this would become too complex, and we should maybe not model this from day 1?

@poettering

Copy link
Copy Markdown
Collaborator

oh, one major think we should add to the spec i think: this describes the json document, but doesn't actually say how to get it.

i.e. systemd's tools currently expose "--introspect-cli" for this, in all our tools. I think we should make that official, i.e. and suggest that any tool should dump its introspection that way if it supports introspection.

and while we are at it, maybe we should also recommend file suffix to use? in #213 i used .Uapi16Manifest as recommended suffix. We could take inspiration from that, hence .Uapi18CliIntrospection or so?

(i sense we might eventually see generators that use a json file as input and generate a parser from it, hence we should recommend a way to name these files from day one?

keszybz and others added 4 commits September 23, 2026 13:25
We had both "cli" and "command" that described the same concept.
Let's use "command" as the primary name, since that is what is used
in the JSON. "Command line interface" is mentioned once as an explanation.
This is needed, since "cli-introspection" is present in the name of
the format and mediaType.

Rename "optional arguments" to just "options". The problem with the
old name is that arguments may also be optional, in the sense of being
present or not. And then the naming becomes *exteremely* confusing.
We now have "options" and "positional arguments". This is the common
naming that is widely used.

The description of splitting is reworked to say that the program
received already-split arguments. I think this was a logical error
in the previous text, since programs on Linux do not do splitting of
args, it is handled by the caller. (Windows is different, but we're
not trying to describe Windows programs. If we wanted to, we'd need
to describe commandline splitting.)

The array of strings received by the program is called an "argument array".
And the items in it are "arguments".

Call the whole program "program". "Binary" is a bad name because
it implies compiled programs.

Call the thing that is the thing after the option a "value".
Previously, the name "argument" was reused, but that makes everything
very confusing. Since "argument" was renamed to "value", the field is
also renamed, so an option has "value":"required|optional|no",
which I think is much nicer. It also matches "valueName" nicely.

For postional args, I changed the awkward "argument=required|optional"
to "isRequired". The default is false, so this can be omitted in most
cases. (Just "required" would be another option. But the variant with
"is" matches "isDeprecated".)

Also, fixed some minor grammar and markup issues. Removed some
parts of definitions that duplicate nearby text.

I moved the description of nesting to a separate Limits subsection.
We'll want to put limits on the length of everything, e.g. strings
and arrays, so having a single section to describe all of that will
be easier to manage.
Co-authored-by: Jörg Behrmann <behrmann@physik.fu-berlin.de>
I think the quotes will make the rendered text easier to read.
@septatrix

Copy link
Copy Markdown

Happy to clarify this if this is not obvious as it might be lost among the discussion here (and also in the PR by @keszybz that is open on this PR in my fork of the repo), but the schema is recursive, so a command can have commands as arguments, this is the intended mechanism to capture subcommands like git does them.

I think I'll try to to (a subset) of ip as another example, as that is a sufficiently complex one to stress test this.

Yes I think that would be great. git might also be a contender for these nested things as an example of a well known CLI tool.

Oh and on another note: Many argument-parsing libraries which provide tab completion support, work by allowing to invoke the command with the currently spelled out flags/options and passing in another special flag (or environment variable) and then returning the list of feasible options.

That is intended to be captured by the dynamic attribute for options, but maybe something similar for arguments would make sense as well.

Currently the dynamic attribute mentions that outputs need to be a single value per line. My though was that instead of printing just the values (or as an alternative) it could instead generate rich output (as JSON-SEQ, an array, etc) which again may contain help, value, category etc. Take for example git rev-parse which in fish generates a tabular autocompletion where next to the value I can see "Local/Remote branch", "Head", "Remote alias", or "Tag" for those respective things and commit hashes with their first commit message line.

image

and while we are at it, maybe we should also recommend file suffix to use? in #213 i used .Uapi16Manifest as recommended suffix. We could take inspiration from that, hence .Uapi18CliIntrospection or so?

I personally find those uppercase extensions quite ugly because they are used effectively nowhere else. I'd rather see .uapi16-manifest/.uapi18-cli-introspection or something along those lines

(i sense we might eventually see generators that use a json file as input and generate a parser from it, hence we should recommend a way to name these files from day one?

Hm, most people I know and work with have a lot of reservations against code generators, I think the other way around will be more likely. However, I work mostly with higher level languages like Python, Rust and JS where introspection is a lot simpler/more powerful and things are done the other way 'round (generate specs from the code).


Another thing that might be worth taking a look into is to specify this as a JSON schema

This output can be used,
among other things,
to check whether a program has a given option or to automatically generate
command-line completion for different shells.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For generating documentation?

Comment thread specs/cli_introspection.md Outdated
Further names can be added as aliases,
e.g. for backward compatibility.

`version` is a an array of strings describing the version of the program.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not plural? The rest seems to be?

Comment thread specs/cli_introspection.md Outdated
"valueName": "",
"sections": [""],
"values": [<value object>],
"isDeprecated": false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to encode if an option can be repeated? Or can pass multiple of? For example:

podman allows passing multiple volumes

@behrmann

behrmann commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Currently the dynamic attribute mentions that outputs need to be a single value per line.

I'd say that this is out of scope for now, but I think this could be added later, e.g. by a new dynamic-json.

Comment on lines +433 to +434
`continueWith` is a string that if set to a non-empty value describes
a different program whose command line introspection should be used for subsequent arguments.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is generic enough. Quite often the following parts are an arbitrary command, so we don't know the command name.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would only know if you know what comes afterwards now, as we do for mkosi vm, we would need something like continueWithFirst that looks at the next word?

Comment thread specs/cli_introspection.md Outdated
@cgwalters cgwalters closed this Oct 2, 2026
@cgwalters

Copy link
Copy Markdown

I think one thing that would likely help this is a PoC that looks like https://docs.rs/clap_complete/latest/clap_complete/ but generates this instead. There's a ton of Rust CLI binaries, and clap is quite expressive, and some of the corner cases there would likely show up in trying to map those to this.

@cgwalters cgwalters reopened this Oct 2, 2026
@cgwalters

Copy link
Copy Markdown

(Sorry, misclick on the closing obviously)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

7 participants