skills/completion-script-generator/SKILL.md
Generates shell completion scripts for CLI tools. Parses command structure from help output, commander/yargs configs, or manual specification. Produces completions for bash, zsh, and fish that follow each shell's conventions.
npx skillsauth add curiositech/windags-skills completion-script-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate shell completion scripts that make CLIs feel native. Parses existing command structure from source code, help output, or manual specification, then produces idiomatic completions for bash, zsh, and fish. Handles subcommands, flags with arguments, mutually exclusive options, file completions, and dynamic completions that query the tool at runtime.
Use for:
--help output when source code is unavailableDo NOT use for:
beautiful-cli-design)Before generating completions, you need the command tree. Three extraction methods, from most to least reliable:
Method A: Parse Source Code (Best)
For Node.js CLIs using commander:
# Extract commands and options from commander program
grep -rn '\.command(' src/cli.ts | while read -r line; do
cmd=$(echo "$line" | grep -oP "\.command\(['\"]([^'\"]+)" | sed "s/.*['\"]//")
echo "COMMAND: $cmd"
done
grep -rn '\.option(' src/cli.ts | while read -r line; do
flag=$(echo "$line" | grep -oP "\.option\(['\"]([^'\"]+)" | sed "s/.*['\"]//")
desc=$(echo "$line" | grep -oP "'[^']*'\s*\)" | head -1)
echo "OPTION: $flag -- $desc"
done
For yargs:
# Extract from yargs .command() and .option() calls
grep -rn '\.command(' src/ --include="*.ts" --include="*.js" | \
grep -oP "command\(['\"]([^'\"]+)" | sed "s/command(['\"]//;s/['\"]$//"
Method B: Parse Help Output (Good)
# Capture help for main command and all subcommands
CLI="wg"
$CLI --help 2>&1 > /tmp/cli-help-main.txt
# Extract subcommands from help output
grep -E "^\s+\w+" /tmp/cli-help-main.txt | awk '{print $1}' | while read -r subcmd; do
$CLI "$subcmd" --help 2>&1 > "/tmp/cli-help-$subcmd.txt" 2>/dev/null
done
# Extract flags
grep -oP '\-\-[\w-]+' /tmp/cli-help-main.txt | sort -u
Method C: Manual Specification (Fallback)
When neither source nor help is parseable, define the structure manually:
# Define as a simple DSL
cat > /tmp/cli-spec.txt << 'SPEC'
command: execute
--dag <file> DAG definition file
--topology <type> Topology type (dag|swarm|team|blackboard)
--dry-run Show plan without executing
--verbose Enable verbose output
command: skill
subcommand: search
--query <text> Search query
--category <cat> Filter by category
--limit <n> Max results (default: 10)
subcommand: show
<skill-id> Skill identifier
command: history
--limit <n> Number of entries
--format <fmt> Output format (json|table|text)
SPEC
Bash completions use the complete builtin with a function that populates COMPREPLY.
#!/bin/bash
# Completion for 'wg' CLI
_wg_completions() {
local cur prev opts commands
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# Top-level commands
commands="execute skill history config"
# Global options
global_opts="--help --version --verbose --quiet"
# If completing the first argument, suggest commands
if [ "$COMP_CWORD" -eq 1 ]; then
COMPREPLY=( $(compgen -W "$commands $global_opts" -- "$cur") )
return 0
fi
# Subcommand-specific completions
local subcmd="${COMP_WORDS[1]}"
case "$subcmd" in
execute)
case "$prev" in
--dag)
# Complete with .yaml and .json files
COMPREPLY=( $(compgen -f -X '!*.@(yaml|yml|json)' -- "$cur") )
return 0
;;
--topology)
COMPREPLY=( $(compgen -W "dag swarm team blackboard team-builder recurring workflow" -- "$cur") )
return 0
;;
esac
opts="--dag --topology --dry-run --verbose --target-project"
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
;;
skill)
if [ "$COMP_CWORD" -eq 2 ]; then
COMPREPLY=( $(compgen -W "search show list" -- "$cur") )
elif [ "${COMP_WORDS[2]}" = "search" ]; then
case "$prev" in
--category)
# Dynamic: query the CLI for categories
local categories
categories=$(wg skill categories 2>/dev/null)
COMPREPLY=( $(compgen -W "$categories" -- "$cur") )
return 0
;;
esac
COMPREPLY=( $(compgen -W "--query --category --limit --format" -- "$cur") )
fi
;;
history)
case "$prev" in
--format)
COMPREPLY=( $(compgen -W "json table text" -- "$cur") )
return 0
;;
esac
COMPREPLY=( $(compgen -W "--limit --format" -- "$cur") )
;;
esac
return 0
}
complete -F _wg_completions wg
Installation for bash:
# Option 1: Source in .bashrc
echo 'source /path/to/wg-completion.bash' >> ~/.bashrc
# Option 2: Install to system completions directory
sudo cp wg-completion.bash /etc/bash_completion.d/wg
# Option 3: CLI self-install
wg completion bash > ~/.local/share/bash-completion/completions/wg
Zsh completions are more powerful: they support descriptions, grouping, and argument types. The format is more complex but the UX is dramatically better.
#compdef wg
# Completion for 'wg' CLI
_wg() {
local -a commands
commands=(
'execute:Execute a DAG or topology'
'skill:Search and manage skills'
'history:View execution history'
'config:Manage configuration'
)
_arguments -C \
'(-h --help)'{-h,--help}'[Show help]' \
'(-v --version)'{-v,--version}'[Show version]' \
'--verbose[Enable verbose output]' \
'--quiet[Suppress non-essential output]' \
'1: :->command' \
'*:: :->args'
case $state in
command)
_describe -t commands 'wg commands' commands
;;
args)
case $words[1] in
execute)
_wg_execute
;;
skill)
_wg_skill
;;
history)
_wg_history
;;
esac
;;
esac
}
_wg_execute() {
_arguments \
'--dag[DAG definition file]:file:_files -g "*.{yaml,yml,json}"' \
'--topology[Topology type]:topology:(dag swarm team blackboard team-builder recurring workflow)' \
'--dry-run[Show execution plan without running]' \
'--verbose[Enable verbose output]' \
'--target-project[Target project directory]:directory:_directories' \
'--env-file[Environment file]:file:_files -g "*.env*"'
}
_wg_skill() {
local -a skill_commands
skill_commands=(
'search:Search skills by query'
'show:Show skill details'
'list:List all available skills'
)
_arguments -C \
'1: :->skill_command' \
'*:: :->skill_args'
case $state in
skill_command)
_describe -t skill_commands 'skill commands' skill_commands
;;
skill_args)
case $words[1] in
search)
_arguments \
'--query[Search query]:query:' \
'--category[Filter by category]:category:_wg_categories' \
'--limit[Max results]:count:' \
'--format[Output format]:format:(json table text)'
;;
show)
_arguments \
'1:skill:_wg_skill_ids'
;;
esac
;;
esac
}
# Dynamic completion: query CLI for skill IDs
_wg_skill_ids() {
local -a skills
skills=(${(f)"$(wg skill list --format=completion 2>/dev/null)"})
_describe -t skills 'skills' skills
}
# Dynamic completion: query CLI for categories
_wg_categories() {
local -a categories
categories=(${(f)"$(wg skill categories 2>/dev/null)"})
_describe -t categories 'categories' categories
}
_wg_history() {
_arguments \
'--limit[Number of entries]:count:' \
'--format[Output format]:format:(json table text)'
}
_wg "$@"
Installation for zsh:
# Option 1: Place in fpath directory
cp _wg ~/.zsh/completions/_wg
# Ensure fpath includes this directory in .zshrc:
# fpath=(~/.zsh/completions $fpath)
# Option 2: CLI self-install
wg completion zsh > "${fpath[1]}/_wg"
# After installing, rebuild completion cache
rm -f ~/.zcompdump && compinit
Fish completions are the simplest to write and the most readable. Each completion is a single complete command.
# Completion for 'wg' CLI
# Disable file completion by default
complete -c wg -f
# Top-level commands
complete -c wg -n __fish_use_subcommand -a execute -d 'Execute a DAG or topology'
complete -c wg -n __fish_use_subcommand -a skill -d 'Search and manage skills'
complete -c wg -n __fish_use_subcommand -a history -d 'View execution history'
complete -c wg -n __fish_use_subcommand -a config -d 'Manage configuration'
# Global options
complete -c wg -l help -s h -d 'Show help'
complete -c wg -l version -s v -d 'Show version'
complete -c wg -l verbose -d 'Enable verbose output'
complete -c wg -l quiet -d 'Suppress non-essential output'
# execute subcommand
complete -c wg -n '__fish_seen_subcommand_from execute' -l dag -r -F -d 'DAG definition file'
complete -c wg -n '__fish_seen_subcommand_from execute' -l topology -r -xa 'dag swarm team blackboard team-builder recurring workflow' -d 'Topology type'
complete -c wg -n '__fish_seen_subcommand_from execute' -l dry-run -d 'Show plan without executing'
complete -c wg -n '__fish_seen_subcommand_from execute' -l target-project -r -xa '(__fish_complete_directories)' -d 'Target project directory'
# skill subcommand
complete -c wg -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from search show list' -a search -d 'Search skills'
complete -c wg -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from search show list' -a show -d 'Show skill details'
complete -c wg -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from search show list' -a list -d 'List all skills'
# skill search options
complete -c wg -n '__fish_seen_subcommand_from skill; and __fish_seen_subcommand_from search' -l query -r -d 'Search query'
complete -c wg -n '__fish_seen_subcommand_from skill; and __fish_seen_subcommand_from search' -l category -r -xa '(wg skill categories 2>/dev/null)' -d 'Category filter'
complete -c wg -n '__fish_seen_subcommand_from skill; and __fish_seen_subcommand_from search' -l limit -r -d 'Max results'
complete -c wg -n '__fish_seen_subcommand_from skill; and __fish_seen_subcommand_from search' -l format -r -xa 'json table text' -d 'Output format'
# skill show - dynamic skill ID completion
complete -c wg -n '__fish_seen_subcommand_from skill; and __fish_seen_subcommand_from show' -xa '(wg skill list --format=completion 2>/dev/null)'
# history subcommand
complete -c wg -n '__fish_seen_subcommand_from history' -l limit -r -d 'Number of entries'
complete -c wg -n '__fish_seen_subcommand_from history' -l format -r -xa 'json table text' -d 'Output format'
Installation for fish:
# Fish completions auto-load from this directory
cp wg.fish ~/.config/fish/completions/wg.fish
# Or CLI self-install
wg completion fish > ~/.config/fish/completions/wg.fish
The gold standard: the CLI generates and installs its own completions.
// In your CLI's completion command handler
import { writeFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { execSync } from 'child_process';
function installCompletions(shell: 'bash' | 'zsh' | 'fish'): void {
const script = generateCompletionScript(shell);
switch (shell) {
case 'bash': {
const dir = join(homedir(), '.local/share/bash-completion/completions');
execSync(`mkdir -p "${dir}"`);
writeFileSync(join(dir, 'wg'), script);
console.log(`Bash completions installed. Restart your shell or run: source ${join(dir, 'wg')}`);
break;
}
case 'zsh': {
const dir = join(homedir(), '.zsh/completions');
execSync(`mkdir -p "${dir}"`);
writeFileSync(join(dir, '_wg'), script);
console.log(`Zsh completions installed. Run: rm -f ~/.zcompdump && compinit`);
break;
}
case 'fish': {
const dir = join(homedir(), '.config/fish/completions');
execSync(`mkdir -p "${dir}"`);
writeFileSync(join(dir, 'wg.fish'), script);
console.log(`Fish completions installed. Available immediately.`);
break;
}
}
}
Auto-detect the user's shell:
# Detect current shell
case "$SHELL" in
*/bash) shell="bash" ;;
*/zsh) shell="zsh" ;;
*/fish) shell="fish" ;;
*) echo "Unknown shell: $SHELL"; exit 1 ;;
esac
| Shell | Market Share (dev) | Effort | Priority | |-------|-------------------|--------|----------| | zsh | ~60% (macOS default) | High (complex format, best UX) | Must have | | bash | ~30% (Linux default) | Medium | Must have | | fish | ~10% (power users) | Low (simplest format) | Nice to have |
Recommendation: Always generate zsh and bash. Add fish if your users skew toward power-user demographics. Skip if your audience is primarily Windows (use PowerShell completion instead, which is a different system).
| Approach | When to Use | Tradeoff | |----------|------------|----------| | Static | Fixed option lists, enum values | Fast, no runtime dependency | | Dynamic | Skill IDs, project names, resource lists | Slower, always current | | Cached dynamic | Large lists that change slowly | Best of both, stale risk |
Rule of thumb: If the completion list has fewer than 50 items AND changes less than weekly, make it static. Otherwise, make it dynamic with a cache timeout.
What it looks like: Generate the completion script, commit it, ship it.
Why wrong: Completion scripts are executable code running in the user's shell. A syntax error in a zsh completion function can break the user's entire tab completion system, not just your tool. A bad compdef can interfere with other tools.
Instead: Test completions in a clean shell session. Source the script, type the command prefix, press tab, and verify the suggestions are correct. Test edge cases: empty input, partial input, after flags, after subcommands.
# Test in isolated bash session
bash --norc --noprofile -c 'source ./wg-completion.bash && complete -p wg'
# Test in isolated zsh session
zsh -f -c 'fpath=(. $fpath); autoload -Uz compinit; compinit; source ./_wg'
What it looks like: Dynamic completion for --project runs wg list-projects which takes 3 seconds because it queries a remote API.
Why wrong: Tab completion must feel instant (<200ms). A 3-second delay on every tab press makes the CLI feel broken. Users will disable completions rather than wait.
Instead: Cache dynamic completions. The CLI should write a cache file that completions read directly. The cache is updated on relevant commands (e.g., wg project create refreshes the project cache), not on every tab press.
# In the completion function, use cache
_wg_projects() {
local cache_file="${XDG_CACHE_HOME:-$HOME/.cache}/wg/projects.txt"
if [ -f "$cache_file" ] && [ $(( $(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file") )) -lt 3600 ]; then
# Cache is fresh (< 1 hour)
COMPREPLY=( $(compgen -W "$(cat "$cache_file")" -- "$cur") )
else
# Cache stale, regenerate
local projects=$(wg project list --format=names 2>/dev/null)
mkdir -p "$(dirname "$cache_file")"
echo "$projects" > "$cache_file"
COMPREPLY=( $(compgen -W "$projects" -- "$cur") )
fi
}
What it looks like: Top-level commands complete, but subcommand options do not.
Why wrong: Users rely on completion most when they are learning a CLI. If wg skill completes but wg skill search -- does not, the user loses trust in completions and stops using them entirely. Partial completions are worse than no completions because they set an expectation and then violate it.
Instead: If you generate completions, generate them completely. Every subcommand, every flag, every argument type. If a command accepts a file, specify the file extension filter. If a flag takes an enum, list the enum values. Thoroughness is not optional.
What it looks like: Completion script contains /Users/eric/dev/wg/skills/ as a path.
Why wrong: Completions are distributed to other machines and users. Hardcoded paths break immediately for anyone else. They also break when the original user moves the project.
Instead: Use the CLI itself for dynamic resolution (wg skill list), environment variables ($WG_HOME), or XDG conventions ($XDG_DATA_HOME/wg/). Never embed absolute paths from the development machine.
Before shipping completions:
--verbose and --quiet)wg completion <shell> or wg completion install)This skill produces:
completion subcommandreferences/shell-completion-conventions.md -- Platform-specific conventions for completion file locations and namingreferences/dynamic-completion-patterns.md -- Patterns for cached dynamic completions across bash, zsh, and fishtools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.