Skip to content

complete ​

A complete node supplies candidates for an argument or flag with the given name. Use a built-in type for paths and other common values, run for a command that prints candidates, or delegate to hand a wrapped command's arguments to that command's own shell completion. For a fixed list, put choices on the argument or flag instead, and for values a command lists that should also be shown in help and enforced, use choices run=.

kdl
// use a custom completion command for all args named "plugin"
complete "plugin" run="mycli plugins list"

Inside an arg ​

A complete can also sit inside the arg it completes, including the arg of a flag. It leaves out the name, since the node it sits in already says which argument it is for:

kdl
arg "<plugin>" {
  complete run="mycli plugins list"
}
flag "--color <when>" {
  arg "<when>" {
    complete type="none"
  }
}
// shorthand for a complete inside the flag's arg
flag "--out <path>" {
  complete type="dir"
}

A named complete covers every argument with that name. One inside an arg covers only that arg, and it wins when both are present. Otherwise usage looks for a named one: first at the top level of the spec, then in the command being completed.

type — completions usage supplies itself ​

Instead of a run, a completer can name something usage already knows how to complete. run, type and delegate are alternatives; setting more than one is an error.

kdl
complete "path" type="file"
complete "manifest" type="path:toml,yaml"
complete "key" type="config_keys"
complete "value" type="config_values"

Append a comma-separated extension list to path: or file: to keep directory traversal while offering only matching files. Extensions omit the leading dot; for example, type="path:toml,yaml" offers directories plus .toml and .yaml files.

typecompletes
file, pathfiles and directories, relative to the working directory
dirdirectories only
executableexecutable paths
commandcommands known to the shell, including names found on PATH
command_argsa command for the first value, then ordinary argument paths
config_keysthe settings this spec's config block declares
config_valuesthe values accepted by the setting named earlier on the command line
usernameuser names, from the environment and /etc/passwd
hostnamehost names, from the environment and /etc/hosts
none, url, emailnothing — the value has no completable candidates, and the file fallback is suppressed
unknownnothing, but the shell's normal fallback stays available

The types below config_values exist mostly so specs generated from clap can carry its ValueHints. username and hostname offer real candidates; none, url, email, and unknown are hint passthroughs with no candidates of their own.

An arg or flag with no completer of its own falls back to file unless its declared choices say otherwise, so type="file" is only worth writing to be explicit.

Completing settings ​

config_keys and config_values are what a config get/config set pair wants, and they need no run: the spec already says what the keys are and what each accepts.

kdl
complete "key" type="config_keys"
complete "value" type="config_values"

cmd "config" {
    cmd "set" {
        arg "<key>"
        arg "<value>"
    }
}

config_keys offers every prop in the block, dotted keys and all, with its help as the description. A hided setting is left out; a deprecated one is still offered — a config file in the wild names it, so it must be completable — with its description saying so.

config_values looks back along the command line for the last word that names a setting, so it does not matter where the key sits relative to the cursor. It offers that setting's choices with their own help, or true and false for a boolean. For anything else it stays quiet and the file fallback applies, which is what a path-valued setting wants anyway.

A key is matched by every name the setting answers to, not only the one it is declared under: an alias resolves to the setting that declares it, and a renamed_to chain is followed to the setting that now holds the values. Those are the names a config file in the wild still carries, so they are the ones a user types.

Both are closed: where the spec enumerates the candidates, a prefix matching none of them completes to nothing rather than falling back to filenames. A setting whose values the spec does not enumerate keeps the fallback — including a union like bool|path, where true and false are offered but a path is still valid, so a path prefix still completes.

Descriptions are reduced to their first line, since one candidate is one row of a menu.

delegate — another command's own completion ​

A wrapper that forwards its trailing words to another program can hand completion of those words to that program's shell completion, instead of reimplementing it with run:

kdl
arg "<layer>" help="Layer to run"
arg "<command>" var=#true help="Terraform command to run"
complete "command" delegate="terraform"

Completing any word of command asks the user's shell what it would offer for terraform followed by the words command already holds: wrapper layer1 plan -o<Tab> offers what terraform plan -o<Tab> would, such as -out, with descriptions where the shell has them. delegate is a command line, so it can carry fixed arguments: delegate="kubectl --context prod". run, type and delegate are alternatives; setting more than one is an error.

The typed words are passed to the shell as data, never spliced into a script, so there is nothing to quote. The shell gets 3 seconds to answer. If it cannot — the shell or the command's completion is not installed, or it times out — the argument gets no delegated candidates and the usual fallback to file names applies.

A word starting with - is offered to the wrapped command once command holds at least one word, because the parser binds a flag this CLI does not declare to that argument. This CLI's own flags stay on offer beside the wrapped command's, since the parser still gives those to this CLI. To pass every word after the first straight through, both when parsing and when completing, add double_dash="automatic" to the argument.

Which shells can be asked, and where the wrapped command's completion has to be registered for them to find it:

  • fish: anywhere fish looks — its completions directories or config.fish. fish only loads completions for a command that is installed.
  • bash: a file bash-completion's loader finds, such as ~/.local/share/bash-completion/completions/<command>. Requires bash-completion. A complete line in ~/.bashrc is not seen; put it in that file instead.
  • zsh: a _<command> function on zsh's default fpath (such as /usr/share/zsh/site-functions), or on $FPATH when that is exported. An fpath set up in ~/.zshrc, and bashcompinit complete lines there, are not seen.
  • PowerShell and Nushell: not supported. The argument gets no delegated candidates.

The shell's configuration is not loaded in bash and zsh because it runs on every Tab and may be slow or print to the terminal; fish's config.fish is loaded, as fish always does. zsh keeps its compinit dump in ${XDG_CACHE_HOME:-~/.cache}/usage/delegate.zcompdump.

Descriptions ​

If you set descriptions=#true, you can provide descriptions for the completions:

kdl
complete "plugin" run="mycli plugins list" descriptions=#true

Results will be split on ":" with the first part being the completion value and the second part being the description, e.g.:

user:User's full name
port:Port number

":" can be escaped with a backslash.

Templates ​

The run can be customized with tera templates. The following values are available:

  • words: A list of all words currently in the prompt. Individual words can be accessed words[1]
  • CURRENT: The index of the word currently being typed, combine with words to get the current word e.g. words[CURRENT].
  • PREV: The index of the previous word in the prompt (CURRENT-1), combine with words to get the previous word e.g. words[PREV].

Values interpolated into run are not escaped automatically. Pass every typed word that becomes one shell argument through shell_quote; the filter emits one POSIX-shell-safe word, including spaces, quotes, substitutions, and command separators as literal data:

kdl
complete "package" run="mycli complete --query={{ words[CURRENT] | shell_quote }}"

Leaving off the filter is appropriate only when the interpolation is deliberately shell syntax. shell_quote accepts strings; quote list members individually rather than quoting a joined command line.

When a child process needs the complete argv as one command-line string, shell_join preserves the original word boundaries before shell_quote makes that command line one inert argument:

kdl
complete "arg" run="mycli __complete --line={{ words | shell_join | shell_quote }}"

Example of completing the second argument based on the first:

kdl
arg "<module>"
arg "<controller>"
complete "module" run="ls modules"
complete "controller" run="ls modules/{{ words[PREV] | shell_quote }}/controllers"

Example of using multiple words (one, two, three) for the completions of the fourth argument:

kdl
arg "<one>"
arg "<two>"
arg "<three>"
arg "<four>"
complete "four" run="echo {{ words | slice(start=-4) | join(sep='\"\n\"') }}"

Here we just use simple commands like ls and echo but these words could be passed to any command.

Which shell runs run ​

run is executed with sh -c, so it is a POSIX shell command line: pipelines, ; sequences and shell builtins all work.

On Windows a POSIX shell is not guaranteed. usage still runs sh -c when sh is on PATH (Git for Windows provides it), and falls back to cmd /c when it is not. cmd cannot run any of the above — it only handles a plain command invocation — so a spec that targets Windows should either keep run to a single command or state that it needs a POSIX shell.

The script is run with stdin closed and stderr inherited, and with __USAGE set to the usage version so a script can tell it was invoked by usage.

MIT LicenseCopyright © 2026jdx.dev