Configuration
A config block describes a CLI's settings: what they are called, what they hold, where a value can come from, and what to say about them in documentation. It is what usage g markdown renders as a settings reference, what a JSON schema for the config file is generated from, and what a runtime resolves values against.
config {
prop "jobs" type="uint" default=0 help="Number of parallel jobs" {
cli "--jobs" "-j"
env "MYCLI_JOBS"
}
}Keys are dotted paths. prop blocks do not nest — write prop "status.missing_tools" rather than a status block containing a missing_tools one — because one spelling keeps merging, ordering and round-tripping unambiguous, and a schema generator can always re-nest on the dots.
Where values come from
Highest precedence first, and only the layers a CLI actually has:
- the command line
- the environment
- config files, nearest first, with a
.localvariant outranking its base - the user's own configuration
- files installed for the whole machine
- the default a
propdeclares
A property can name its sources explicitly; the order they are written in is the order they are consulted.
config {
prop "check" type="bool" {
cli "--check" // flags that set it, as declared elsewhere in this spec
env "HK_CHECK" "HK_LINT" // several: aliases, highest precedence first
deprecated_env "HK_VERIFY" // read last, with a warning
source "git" "hk.check" // a source kind declared below
}
}source — kinds usage does not know about
usage reads the command line, the environment, and config files. Everything else — a git config, a pkl file, an .npmrc — is a kind the CLI reads itself and declares here so documentation can describe it. {key} and {value} are substituted.
config {
source "git" name="git config" doc_hint="git config `{key}`" \
set_hint="git config {key} {value}"
source "pkl" name="hk.pkl"
}Docs then render a property bound to that kind as "settable with git config hk.check", without usage having any idea what git is.
file — where config files live
In ascending precedence: the last one named wins. This is the chain that rc-style merging walks, and writing it down is what lets documentation describe it accurately.
config {
file "/etc/mycli/config.toml" scope="system"
file "~/.config/mycli/config.toml" scope="global"
file "mycli.toml" findup=#true
file "mycli.local.toml" findup=#true
file ".myclirc" format="yaml"
}| property | meaning |
|---|---|
findup | look for this name in the current directory and every parent |
scope | project (default), global, or system |
format | when the extension does not say: toml, json, yaml |
scope is not decoration: a prop marked scope="global" refuses values from project files, so a setting a repository must not be able to change can say so.
prop — the settings
| property | meaning |
|---|---|
type | the type, see below. Defaults to string |
env | an environment variable that sets it — the child node form below takes several |
default | the value when nothing supplies one — a typed KDL value: 4, #true, "x" |
optional | explicitly permit or require absence; otherwise inferred from the type and default |
default_note | what to print instead of the default, when the real one needs explaining |
help, long_help | one line, and the whole markdown story |
help_heading | the section to list it under in generated docs |
merge | replace (default), union for collections, deep for maps |
scope | any (default), global (never from a project file), env (never from a file) |
deprecated | why not to use it any more |
deprecated_warn_at, deprecated_remove_at | CLI versions that start warnings and stop accepting configured values |
renamed_to | the property that replaces this one, so an old key folds into the new |
hide | keep it out of docs and completions |
since | the version that introduced it |
parse | a named parser for one string: list_by_comma, list_by_colon, list_by_os_path_separator, set_by_comma |
writes_to | where config set should write it, when that is not the usual file |
And as child nodes, for anything multi-valued or long:
| node | meaning |
|---|---|
cli "--jobs" "-j" | flags that set it |
env "A" "B" | environment variables, highest precedence first |
deprecated_env "OLD_A" | environment aliases read last and reported as deprecated |
alias "old.key" | equivalent config keys, accepted without a deprecation warning |
source "git" "a.b" | its keys in a declared source kind |
default "a" "b" | a list default, values typed as written (default 80 443) |
long_help "…" | the long form, when a raw string reads better than a property |
example "…" | one invocation worth showing |
choices { choice "a" help="…" } | the values it accepts, each with its own help |
x "ns.key" value | see extensions |
Deprecation gates
deprecated_warn_at withholds a setting's deprecation warning until the running CLI version reaches that release. At deprecated_remove_at, configured values for the setting are ignored and reported as removed; resolution continues, as it does for an unknown setting, and a declared default remains available to the compiled settings type.
The CLI version is runtime context, not the version of usage-config. Rust callers pass it explicitly with resolve_with_context and ResolutionContext::for_cli_version. The compatibility resolve entry point has no version context: it warns but keeps the value. An unreadable CLI version or milestone does the same, so uncertainty neither hides the deprecation nor silently changes configuration.
Versions follow the same rule as argv deprecations: dotted numeric segments support semver and calver, missing segments are zero, prereleases precede their release, and build metadata is ignored.
Types
base := bool | string | int | uint | float | path | url | duration | object
type := base | list<type> | set<type> | map<base, type> | option<type> | type "|" typeoption<T> means absent is a legitimate state with no default standing in. A union — bool|string — records that a setting takes either; nothing here validates which.
A name this version of usage does not know is kept as written rather than refused, so a spec can name a type only its own tool understands and still load everywhere else. Anything consuming a type it cannot interpret treats it as a string.
For compatibility, data_type is still read as a spelling of type, and the older names (boolean, integer, number, usize, array<…>, optional<…>) are accepted.
Extensions
A tool often needs to carry something usage has no opinion about — which Rust type a setting deserializes into, which file a write should be routed to, an enterprise policy. x nodes hold it: preserved in order, written back out unchanged, present in usage g json, and interpreted by nothing in usage.
config {
prop "python.uv_venv_auto" type="bool|string" {
x "mise.rust_type" "PythonUvVenvAuto"
x "mise.parse_env" "bool_string"
}
}This is the seam that lets a CLI with special rules describe its settings here without usage having to model those rules.
Completing settings
A config get/config set pair completes from the block without a run of its own — see complete:
complete "key" type="config_keys"
complete "value" type="config_values"Splitting it out
A CLI with many settings does not want them inline. include reads another file, resolved relative to the one naming it:
name "mise"
bin "mise"
include file="./settings.usage.kdl"Compatibility
The parser refuses vocabulary it does not know, so a spec using anything on this page must say which version of usage it needs:
min_usage_version "6.7.1"Choose the oldest release that supports the nodes your spec uses; 6.7.1 is an example, not a requirement for every config block. The version declaration makes the requirement visible before an older consumer encounters unsupported syntax.