You are writing a zsh-only completion script for the Dokku CLI. Use the installed dokku binary and the configured local harness (or any live Dokku client context) so goals and argument values are discovered from the current Dokku system—not hard-coded.
Produce a single zsh completion implementation that:
- Registers for the command name
dokku(compdefand/or#compdef+fpath, as appropriate for zinit / oh-my-zsh style loading). - Completes goals after
dokku(e.g.apps:create,config:set,postgres:info,redis:create). - Completes each goal's arguments contextually: app names, network names, plugin service instances, domains, config keys, flags, enumerated flag values, and other values exposed by the connected Dokku system.
- Stays pure zsh — no
bashcompinit, no bash-completion polyfills, no wrapping the upstream bash script. - Discovers goals and candidate values dynamically from a live
dokku. Newly installed plugins and newly created or removed resources must appear without editing the completion file. - Never embeds inventories of server resources. App names, networks, services, domains, keys, and similar candidates must always originate from queries against the current Dokku context.
Write the completer to completions/_dokku in this repo (#compdef dokku at the top). The plugin loader (zsh-dokku.plugin.zsh) only adds completions/ to fpath; do not rely on source-time compdef unless you document why.
This repo provides a local Dokku playground. Prefer it when available:
just bootstrap_local_dokku # reset + start harness + example app + redis + postgres
# mise: PATH includes ./bin so `dokku` is the docker-exec wrapper
dokku --quiet help --all
dokku plugin:list
dokku apps:list| Env | Meaning |
|---|---|
DOKKU_CONTAINER |
Compose container (dokku-completion-harness) |
DOKKU_HOST |
dokku-completion-harness.orb.local (remote client) |
DOKKU_PORT |
22 via OrbStack |
If the harness is down, any working client is fine: Homebrew dokku with DOKKU_HOST / DOKKU_PORT, or a git remote named dokku.
Verify the client is configured before trusting help output. Unconfigured remote client prints a setup error that includes an indented i.e. line—naive parsers will treat that as a goal.
Match upstream bash completion’s idea of “commands”:
# Official bash completer:
# https://github.com/dokku/dokku/blob/master/contrib/bash-completion
dokku --quiet help --all | awk '/^ /{ print $1 }'
- Indented help lines are goal tokens (
plugin:subcommandor bare names likeversion). - Plugins add more colon-separated goals automatically when installed on the server.
- Filter aggressively: only tokens matching something like
^[a-z][a-z0-9_-]*(:[a-z0-9_-]+)*$
so setup prose (i.e.) never becomes a match.
Inspect live output yourself (dokku --quiet help --all, plugin:list, sample postgres: / redis: lines) before coding.
Use the selected goal's live help signature to determine its argument shape, then query the connected Dokku system for candidates at the current argument position. Inspect the real output and supported flags before choosing a parser; do not assume headers or formats.
Examples of the intended discovery paths:
| Argument kind | Live source to inspect/query |
|---|---|
| Apps | dokku apps:list and its machine-readable/plain-output options |
| Networks | dokku network:list and its supported output options |
| Postgres services | dokku postgres:list |
| Redis services | dokku redis:list |
| Other plugin services | The installed plugin namespace's live help and *:list command when available |
| Domains | The relevant live domains:* query for the selected/current app |
| Config keys | The relevant live config:* query after resolving the app or --global context |
| Flags and enumerated values | The selected goal's live help/usage output |
These commands are examples of discovery paths, not permission to hard-code their returned values. Prefer stable machine-readable output when the installed Dokku/plugin version offers it; otherwise parse the inspected live output conservatively.
Completion must be positional and context-aware. For example:
dokku apps:destroy <Tab>completes current app names.dokku apps:list <Tab>completes--format; it must not complete app names because the live signature isapps:list [--format stdout|json].- A goal with an optional app and flags, such as
dokku builds:list <Tab>, may offer both live app names and valid flags at the same position. dokku postgres:info <Tab>completes current Postgres service names.dokku postgres:link <Tab>completes Postgres services, while the following argument completes apps.dokku config:get <app> <Tab>completes keys for that app.- A goal expecting a network completes networks from the current Dokku system.
Plugin argument support must be extensible. Discover installed namespaces and their goal signatures from live help. When a plugin exposes a resource-listing command, use it rather than maintaining a static service inventory.
Goals contain colons (apps:create). _describe treats the first : as name:description and will mangle them. Offer the flat list with compadd (e.g. via _wanted + compadd -a).
Real Dokku is dokku apps:create, not dokku apps create. Community completers that assume spaces are wrong for this CLI.
Before caching or completing goals or argument values:
- Non-zero exit or empty stdout → fail (do not clobber a good cache).
- Reject output that looks like the unconfigured-client message (
DOKKU_HOST,git remote add dokku). - Validate and re-filter every candidate type when loading a cache so old pollution self-heals.
- Treat headings, status messages, warnings, and setup prose as non-candidates.
- Never fall back to invented or statically bundled resource names when a live query fails.
- Parse the selected colon-shaped goal and
CURRENTargument position. - Use earlier words when a query depends on context, such as the selected app, service, or
--global. - Obtain argument shapes and available flags from live help wherever practical.
- Do not assume every goal accepts an app. Follow the live signature exactly:
list-all goals such as
apps:listhave no app positional, while goals such asapps:destroydo. - Offer valid flags even when the current word is empty; users should not need
to type
-before discovering options. If both a resource argument and flags are valid at the current position, add both candidate groups. - Once
words[2]is a recognized goal, the completer owns the remaining command line. Terminal positions, intentionally unsupported values, empty live queries, and failed safe lookups must return a handled/success state with zero matches when appropriate. Do not return failure and allow compsys to fall back to top-level Dokku goals, files, or unrelated “primary” completions. - Distinguish “no candidates at this valid position” from “this is not a Dokku completion state.” Graceful no-match behavior means no invented matches and no unrelated fallback matches.
- It is acceptable to encode small amounts of goal-to-resource grammar when Dokku exposes no machine-readable argument schema. It is not acceptable to encode the resource values themselves.
- Query plugin namespaces generically where possible so newly installed service plugins can participate without adding their instance names—or ideally their namespace—to the source.
- Quote all user-selected arguments passed into lookup commands and ensure completion lookups cannot become interactive or destructive.
help --all and resource-list commands may hit SSH or a container. Avoid unnecessary calls, but do not let caching turn the completion into a stale static inventory.
- Suggested path:
${XDG_CACHE_HOME:-$HOME/.cache}/dokku/completion - Goal/help TTL may be on the order of days (e.g. 7).
- Resource-value caches must use a much shorter TTL appropriate for mutable data, or query live on each completion when latency is acceptable.
- Cache entries must be separated by Dokku connection context so values from one host are never offered for another host.
- Include the candidate type and any required parent context in resource cache keys (for example, config keys for a specific app).
- Document force refresh:
rm -f ~/.cache/dokku/completion - Atomic write (temp file +
mv) so a failed refresh leaves the previous cache intact.
Upstream bash uses /var/cache/dokku-completion (server-oriented); prefer an XDG cache path on developer machines.
- Gate on
(( $+commands[dokku] || $+aliases[dokku] ))(or equivalent) so the script no-ops whendokkuis absent. - Register
_dokkufordokku. If load order is post-compinit(zinitwait'0c'),compdef _dokku dokkuand_comps[dokku]=_dokkuat source time is enough—nozcompdumprebuild required for that style.
Required scope:
- Flat list of goals for the first argument after
dokku. - Core + all installed plugins via live help.
- Context-aware completion for app names and multi-argument goals.
- Dynamic network, domain, config-key, and plugin service-instance completion when the connected system exposes a query for them.
- Flags and enumerated values discoverable from live goal usage.
- Graceful no-match behavior for values that Dokku cannot expose safely or reliably.
Do not declare the implementation complete with goal-only completion. Argument completion backed by the current Dokku system is part of the deliverable.
| Approach | Why not |
|---|---|
Source official bash script under bashcompinit |
Needs helpers zsh does not provide (_get_comp_words_by_ref, etc.); high polyfill cost |
| Static goals, resources, plugin namespaces, or candidate values | Rots when Dokku or server state changes; misses server-specific plugins and resources |
MenkeTechnologies-style space-separated _dokku |
Wrong CLI shape (apps create vs apps:create) |
_describe for colon goals |
Breaks on first : |
| Completing resource-shaped placeholders with guessed examples | Invents values that may not exist on the connected Dokku system |
Run these against the harness (or any live Dokku):
# 1. Help yields real goals including plugins
dokku --quiet help --all | awk '/^ /{ print $1 }' | grep -E '^(apps:|postgres:|redis:)' | head
# 2. After loading your completer in a zsh with compsys:
# dokku <Tab> → apps:create, postgres:…, redis:…, etc.
# dokku apps:<Tab> or partial prefixes filter correctly
# 3. Dynamic arguments come from the harness:
# dokku apps:destroy <Tab> → includes the current example app
# dokku postgres:info <Tab> → includes the current Postgres service
# dokku redis:info <Tab> → includes the current Redis service
# applicable network goals → include live network names
# 4. Multi-argument dispatch uses the right resource type:
# dokku postgres:link <service> <Tab> → current app names
# app-scoped config completion → keys from the selected app
# 5. Goal signatures, blank-word flags, and terminal states are respected:
# dokku apps:list <Tab> → --format, not app names/goals/files
# dokku apps:list --format <Tab> → stdout and json
# dokku builds:list <Tab> → live apps plus valid flags
# dokku version <Tab> → no top-level-goal or filename fallback
#
# Automated tests should assert both the candidate set and the completion
# function's return status so an empty handled state cannot regress into
# compsys fallback.
# 6. Create/remove a test resource, refresh or wait for the short TTL, and
# verify candidates change without editing the completion source.
# 7. Unconfigured clients and failed goal/resource queries do not invent junk
# matches, leak another host's values, or offer setup prose such as "i.e."
# 8. Force cache rebuild works
rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/dokku/completion"
# Tab again should repopulate without errorsOptional: echo ${_comps[dokku]} → your completer function name.
| What | Link |
|---|---|
| Official bash completion | https://github.com/dokku/dokku/blob/master/contrib/bash-completion |
| Remote client docs | https://dokku.com/docs/deployment/remote-commands/ |
| Upstream zsh request | dokku/dokku#6023 |
| Community static zsh (reference only) | https://github.com/MenkeTechnologies/zsh-more-completions |
Write the completion as zsh source ready to load. Comment the cache paths, refresh behavior, connection-context isolation, dynamic lookup strategy, and any value types the installed Dokku cannot expose safely. Prefer clear function names (_dokku, _dokku_refresh_completion_cache, _dokku_complete_apps, …). Do not emit bash. Do not depend on this monorepo’s Justfile/compose at runtime—only use them to inspect the live CLI while authoring.