Skip to content

Add shell autocompletion support#915

Open
MaximLogic wants to merge 2 commits into
wardenenv:mainfrom
MaximLogic:main
Open

Add shell autocompletion support#915
MaximLogic wants to merge 2 commits into
wardenenv:mainfrom
MaximLogic:main

Conversation

@MaximLogic

Copy link
Copy Markdown

Check List

  • Matching PR in the documentation repo (replace text with link when it exists)
  • Entry in CHANGELOG.md

Is your feature request related to a problem? Please describe.
Warden CLI has no shell autocompletion, so users must remember all commands, or create aliases manually, which slows down the workflow.

Describe the solution you've submitted
Added bash and zsh completion scripts that dynamically discover available commands from commands/*.cmd files. Completions cover top-level commands, subcommands for db, sync, env, svc, and environment types for env-init. The warden install command now automatically sets up completions by symlinking scripts into ~/.warden/completions/ and updating shell rc files.

Describe alternatives you've considered
Static hardcoded command lists - rejected in favor of dynamic discovery.

Additional context
Tested on macOS with zsh and bash. Completions fall back to a hardcoded list if the commands directory is unavailable.

Note: top-level commands are discovered dynamically from *.cmd files, but subcommands (e.g. db connect|import|dump|upgrade, sync start|stop|list|...) are hardcoded in the completion scripts because they are
defined inside the command files themselves, not as separate discoverable entities. This means the completion scripts will need to be updated manually whenever new subcommands are added to existing commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds shell autocompletion support for the Warden CLI by introducing bash/zsh completion scripts and wiring their installation into warden install, improving discoverability of commands and common subcommands.

Changes:

  • Added bash completion script that completes top-level commands (dynamic) and selected subcommands/env types (static).
  • Added zsh completion function with similar behavior and option descriptions.
  • Updated warden install to symlink completion scripts into ~/.warden/completions/ and append initialization snippets to shell RC files.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
completions/warden.bash New bash completion script with dynamic command discovery + hardcoded subcommands/env types.
completions/_warden New zsh completion function with dynamic command discovery + hardcoded subcommands/env types.
commands/install.cmd Installs completion scripts and modifies user shell RC configuration during warden install.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread completions/_warden
warden_dir=""
if [[ -n "${warden_bin}" ]]; then
local real_bin
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

readlink can return a relative symlink target; if that happens, dirname "${real_bin}" is interpreted relative to the current directory and warden_dir detection can break. Since bin/warden already contains logic to normalize relative symlinks, consider applying a similar normalization here so completion can reliably locate commands/ and environments/.

Suggested change
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"
# Normalize relative symlink targets so dirname/.. resolution is reliable
if [[ "${real_bin}" != /* ]]; then
local warden_bin_dir real_bin_dir real_bin_name
warden_bin_dir="$(dirname "${warden_bin}")"
real_bin_dir="$(dirname "${real_bin}")"
real_bin_name="$(basename "${real_bin}")"
real_bin="$(cd "${warden_bin_dir}/${real_bin_dir}" 2>/dev/null && printf '%s\n' "${PWD}/${real_bin_name}")"
fi

Copilot uses AI. Check for mistakes.
Comment thread completions/warden.bash

_warden() {
local cur prev words cword
_init_completion || return

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The bash completion relies on _init_completion, which is only available when the bash-completion package is installed and sourced. On systems without it, completion will silently fail; consider adding a small fallback to initialize cur/prev/words/cword from COMP_WORDS/COMP_CWORD when _init_completion is unavailable (or detect and emit a helpful message).

Suggested change
_init_completion || return
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion || return
else
words=("${COMP_WORDS[@]}")
cword=${COMP_CWORD}
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
fi

Copilot uses AI. Check for mistakes.
Comment thread completions/warden.bash
Comment on lines +23 to +27
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
commands="$(ls "${warden_dir}/commands/"*.cmd 2>/dev/null \
| xargs -I{} basename {} .cmd \
| grep -v usage)"
fi

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

Top-level command discovery only inspects ${warden_dir}/commands/*.cmd, but the CLI itself also supports commands from ${HOME}/.warden/commands and from a project-local .warden/commands (see bin/warden command resolution order). This means custom commands won’t autocomplete; consider merging results from those additional command locations (when present) so completion matches actual command resolution behavior.

Suggested change
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
commands="$(ls "${warden_dir}/commands/"*.cmd 2>/dev/null \
| xargs -I{} basename {} .cmd \
| grep -v usage)"
fi
# Collect commands from all supported locations:
# - ${warden_dir}/commands
# - ${HOME}/.warden/commands
# - project-local .warden/commands
local cmd_dirs=()
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
cmd_dirs+=("${warden_dir}/commands")
fi
if [[ -n "${HOME:-}" && -d "${HOME}/.warden/commands" ]]; then
cmd_dirs+=("${HOME}/.warden/commands")
fi
if [[ -d ".warden/commands" ]]; then
cmd_dirs+=(".warden/commands")
fi
if [[ ${#cmd_dirs[@]} -gt 0 ]]; then
local dir
for dir in "${cmd_dirs[@]}"; do
while IFS= read -r cmd_name; do
# Filter out the internal "usage" command
[[ "${cmd_name}" == "usage" ]] && continue
commands+="${cmd_name} "
done < <(ls "${dir}/"*.cmd 2>/dev/null \
| xargs -I{} basename {} .cmd)
done
fi

Copilot uses AI. Check for mistakes.
Comment thread completions/_warden
Comment on lines +15 to +19
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
commands=(${(f)"$(ls "${warden_dir}/commands/"*.cmd 2>/dev/null \
| xargs -I{} basename {} .cmd \
| grep -v usage)"})
fi

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

Top-level command discovery only checks ${warden_dir}/commands/*.cmd, but warden also loads commands from ${HOME}/.warden/commands and project-local .warden/commands first. As a result, custom/overridden commands won’t autocomplete even though the CLI can execute them; consider incorporating those additional directories into the commands array when they exist.

Suggested change
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
commands=(${(f)"$(ls "${warden_dir}/commands/"*.cmd 2>/dev/null \
| xargs -I{} basename {} .cmd \
| grep -v usage)"})
fi
commands=()
# Discover commands from project-local, user, and global warden directories (in that order)
local -a cmd_files
cmd_files=()
if [[ -d "${PWD}/.warden/commands" ]]; then
cmd_files+=("${PWD}"/.warden/commands/*.cmd(N))
fi
if [[ -n "${HOME}" && -d "${HOME}/.warden/commands" ]]; then
cmd_files+=("${HOME}"/.warden/commands/*.cmd(N))
fi
if [[ -n "${warden_dir}" && -d "${warden_dir}/commands" ]]; then
cmd_files+=("${warden_dir}"/commands/*.cmd(N))
fi
if (( ${#cmd_files} > 0 )); then
commands=(${(u)${(f)"$(printf '%s\n' "${cmd_files[@]}" \
| xargs -I{} basename {} .cmd \
| grep -v usage)"}})
fi

Copilot uses AI. Check for mistakes.
Comment thread commands/install.cmd
ZSHRC="${HOME}/.zshrc"
if [[ -f "${ZSHRC}" || "$OSTYPE" == "darwin"* ]] && ! grep -q 'warden/completions' "${ZSHRC}" 2>/dev/null; then
echo "==> Adding warden zsh completion to ${ZSHRC}"
printf '\n# Warden CLI zsh completion\nfpath=("%s" $fpath)\nautoload -Uz compinit && compinit\n' \

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

This appends autoload -Uz compinit && compinit into the user’s .zshrc. If the user already initializes completion elsewhere (common), this will run compinit twice on every shell startup, which can slow startup and may trigger additional warnings. Consider only adding the fpath+=(...) line (and optionally a short note telling users to run compinit if needed), or guard the compinit call so it only runs when completion hasn’t been initialized yet.

Suggested change
printf '\n# Warden CLI zsh completion\nfpath=("%s" $fpath)\nautoload -Uz compinit && compinit\n' \
printf '\n# Warden CLI zsh completion\n# Ensure that zsh completion (compinit) is initialized in your shell config.\nfpath=("%s" $fpath)\n' \

Copilot uses AI. Check for mistakes.
Comment thread completions/warden.bash
local warden_dir=""
if [[ -n "${warden_bin}" ]]; then
local real_bin
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

readlink may return a relative symlink target (common on macOS/Homebrew), in which case dirname "${real_bin}" is relative to the current working directory and warden_dir resolution can fail. The main bin/warden script handles relative symlinks explicitly; consider doing the same here (resolve relative targets against dirname "${warden_bin}", or otherwise canonicalize to an absolute path) so dynamic command/env discovery works reliably.

Suggested change
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"
real_bin="$(readlink "${warden_bin}" 2>/dev/null || echo "${warden_bin}")"
# If readlink returned a relative path, resolve it against the warden_bin directory
if [[ "${real_bin}" != /* ]]; then
local warden_bin_dir
warden_bin_dir="$(cd "$(dirname "${warden_bin}")" 2>/dev/null && pwd)"
if [[ -n "${warden_bin_dir}" ]]; then
real_bin="${warden_bin_dir}/${real_bin}"
fi
fi

Copilot uses AI. Check for mistakes.
@navarr navarr moved this to 🏗 In progress in Warden Feb 16, 2026
@navarr navarr added this to the Warden 0.17 milestone Feb 16, 2026
@navarr navarr added the enhancement New feature or request label Feb 16, 2026
Comment thread completions/_warden
_describe 'sync subcommand' sync_cmds
fi
;;
env|svc)

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.

Both of these just pipe what comes next into docker compose (or more specifically the command stored in ${DOCKER_COMPOSE_COMMAND}) - Is there any way to use this built in flag to use the autocomplete for that at this stage? That could give us a much better autocomplete experience here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I tried to do this one with docker compose --help command, try yourself, works perfectly for me so far.

…r subcommands and `docker compose <subcmd> --help` for flags.
@navarr

navarr commented Mar 10, 2026

Copy link
Copy Markdown
Member

@MaximLogic Would you please review the comments left by Copilot and determine if they need handling or if they're hallucinations?

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

Labels

enhancement New feature or request

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

3 participants