diff --git a/.goreleaser.yml b/.goreleaser.yml
index e89fd66f..7cce7dde 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,4 +1,10 @@
 project_name: ybm
+before:
+  hooks:
+    # You may remove this if you don't use go modules.
+    - go mod tidy
+    # generate autocomplete scripts
+    - ./scripts/completions.sh
 builds:
   - env:
       - CGO_ENABLED=0
@@ -25,12 +31,17 @@ changelog:
   sort: asc
   filters:
     exclude:
-      - '^docs:'
-      - '^test:'
+      - "^docs:"
+      - "^test:"
 
 archives:
   - format: zip
     name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
+    files:
+      - README.md
+      - LICENSE
+      - NOTICE
+      - completions/*
 checksum:
   name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS"
   algorithm: sha256
diff --git a/Makefile b/Makefile
index 5739f74f..3410ee6e 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ test:
 
 doc:
 	YBM_FF_TOOLS=true  go run main.go tools gen-doc --format markdown
+	./scripts/completions.sh
 
 build:
 	go build -ldflags="-X 'main.version=v${VERSION}'" -o ${BINARY}
diff --git a/NOTICE b/NOTICE
index c1691573..196d92e6 100644
--- a/NOTICE
+++ b/NOTICE
@@ -62,8 +62,8 @@ The following subcomponents are used:
 ## github.com/cloudflare/circl
 
 * Name: github.com/cloudflare/circl
-* Version: v1.1.0
-* License: [BSD-3-Clause](https://github.com/cloudflare/circl/blob/v1.1.0/LICENSE)
+* Version: v1.3.3
+* License: [BSD-3-Clause](https://github.com/cloudflare/circl/blob/v1.3.3/LICENSE)
 
 
 ## github.com/common-nighthawk/go-figure
@@ -104,8 +104,8 @@ The following subcomponents are used:
 ## github.com/go-logr/logr
 
 * Name: github.com/go-logr/logr
-* Version: v1.2.3
-* License: [Apache-2.0](https://github.com/go-logr/logr/blob/v1.2.3/LICENSE)
+* Version: v1.2.4
+* License: [Apache-2.0](https://github.com/go-logr/logr/blob/v1.2.4/LICENSE)
 
 
 ## github.com/golang-jwt/jwt/v5
@@ -122,11 +122,11 @@ The following subcomponents are used:
 * License: [BSD-3-Clause](https://github.com/google/go-cmp/blob/v0.5.9/LICENSE)
 
 
-## github.com/google/go-github/v50/github
+## github.com/google/go-github/v53/github
 
-* Name: github.com/google/go-github/v50/github
-* Version: v50.2.0
-* License: [BSD-3-Clause](https://github.com/google/go-github/blob/v50.2.0/LICENSE)
+* Name: github.com/google/go-github/v53/github
+* Version: v53.1.0
+* License: [BSD-3-Clause](https://github.com/google/go-github/blob/v53.1.0/LICENSE)
 
 
 ## github.com/google/go-querystring/query
@@ -244,15 +244,15 @@ The following subcomponents are used:
 ## github.com/onsi/ginkgo/v2
 
 * Name: github.com/onsi/ginkgo/v2
-* Version: v2.9.2
-* License: [MIT](https://github.com/onsi/ginkgo/blob/v2.9.2/LICENSE)
+* Version: v2.9.7
+* License: [MIT](https://github.com/onsi/ginkgo/blob/v2.9.7/LICENSE)
 
 
 ## github.com/onsi/gomega
 
 * Name: github.com/onsi/gomega
-* Version: v1.27.6
-* License: [MIT](https://github.com/onsi/gomega/blob/v1.27.6/LICENSE)
+* Version: v1.27.8
+* License: [MIT](https://github.com/onsi/gomega/blob/v1.27.8/LICENSE)
 
 
 ## github.com/pelletier/go-toml/v2
@@ -398,22 +398,22 @@ The following subcomponents are used:
 ## golang.org/x/mod/semver
 
 * Name: golang.org/x/mod/semver
-* Version: v0.9.0
-* License: [BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.9.0:LICENSE)
+* Version: v0.10.0
+* License: [BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE)
 
 
 ## golang.org/x/net
 
 * Name: golang.org/x/net
-* Version: v0.8.0
-* License: [BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.8.0:LICENSE)
+* Version: v0.10.0
+* License: [BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE)
 
 
 ## golang.org/x/oauth2
 
 * Name: golang.org/x/oauth2
-* Version: v0.6.0
-* License: [BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.6.0:LICENSE)
+* Version: v0.8.0
+* License: [BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.8.0:LICENSE)
 
 
 ## golang.org/x/sys/unix
@@ -426,8 +426,8 @@ The following subcomponents are used:
 ## golang.org/x/term
 
 * Name: golang.org/x/term
-* Version: v0.6.0
-* License: [BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.6.0:LICENSE)
+* Version: v0.8.0
+* License: [BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE)
 
 
 ## golang.org/x/text
diff --git a/completions/ybm.bash b/completions/ybm.bash
new file mode 100644
index 00000000..fe6e9755
--- /dev/null
+++ b/completions/ybm.bash
@@ -0,0 +1,325 @@
+# bash completion V2 for ybm                                  -*- shell-script -*-
+
+__ybm_debug()
+{
+    if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
+        echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
+    fi
+}
+
+# Macs have bash3 for which the bash-completion package doesn't include
+# _init_completion. This is a minimal version of that function.
+__ybm_init_completion()
+{
+    COMPREPLY=()
+    _get_comp_words_by_ref "$@" cur prev words cword
+}
+
+# This function calls the ybm program to obtain the completion
+# results and the directive.  It fills the 'out' and 'directive' vars.
+__ybm_get_completion_results() {
+    local requestComp lastParam lastChar args
+
+    # Prepare the command to request completions for the program.
+    # Calling ${words[0]} instead of directly ybm allows to handle aliases
+    args=("${words[@]:1}")
+    requestComp="${words[0]} __complete ${args[*]}"
+
+    lastParam=${words[$((${#words[@]}-1))]}
+    lastChar=${lastParam:$((${#lastParam}-1)):1}
+    __ybm_debug "lastParam ${lastParam}, lastChar ${lastChar}"
+
+    if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
+        # If the last parameter is complete (there is a space following it)
+        # We add an extra empty parameter so we can indicate this to the go method.
+        __ybm_debug "Adding extra empty parameter"
+        requestComp="${requestComp} ''"
+    fi
+
+    # When completing a flag with an = (e.g., ybm -n=<TAB>)
+    # bash focuses on the part after the =, so we need to remove
+    # the flag part from $cur
+    if [[ "${cur}" == -*=* ]]; then
+        cur="${cur#*=}"
+    fi
+
+    __ybm_debug "Calling ${requestComp}"
+    # Use eval to handle any environment variables and such
+    out=$(eval "${requestComp}" 2>/dev/null)
+
+    # Extract the directive integer at the very end of the output following a colon (:)
+    directive=${out##*:}
+    # Remove the directive
+    out=${out%:*}
+    if [ "${directive}" = "${out}" ]; then
+        # There is not directive specified
+        directive=0
+    fi
+    __ybm_debug "The completion directive is: ${directive}"
+    __ybm_debug "The completions are: ${out}"
+}
+
+__ybm_process_completion_results() {
+    local shellCompDirectiveError=1
+    local shellCompDirectiveNoSpace=2
+    local shellCompDirectiveNoFileComp=4
+    local shellCompDirectiveFilterFileExt=8
+    local shellCompDirectiveFilterDirs=16
+
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
+        # Error code.  No completion.
+        __ybm_debug "Received error from custom completion go code"
+        return
+    else
+        if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
+            if [[ $(type -t compopt) = "builtin" ]]; then
+                __ybm_debug "Activating no space"
+                compopt -o nospace
+            else
+                __ybm_debug "No space directive not supported in this version of bash"
+            fi
+        fi
+        if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+            if [[ $(type -t compopt) = "builtin" ]]; then
+                __ybm_debug "Activating no file completion"
+                compopt +o default
+            else
+                __ybm_debug "No file completion directive not supported in this version of bash"
+            fi
+        fi
+    fi
+
+    # Separate activeHelp from normal completions
+    local completions=()
+    local activeHelp=()
+    __ybm_extract_activeHelp
+
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
+        # File extension filtering
+        local fullFilter filter filteringCmd
+
+        # Do not use quotes around the $completions variable or else newline
+        # characters will be kept.
+        for filter in ${completions[*]}; do
+            fullFilter+="$filter|"
+        done
+
+        filteringCmd="_filedir $fullFilter"
+        __ybm_debug "File filtering command: $filteringCmd"
+        $filteringCmd
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
+        # File completion for directories only
+
+        # Use printf to strip any trailing newline
+        local subdir
+        subdir=$(printf "%s" "${completions[0]}")
+        if [ -n "$subdir" ]; then
+            __ybm_debug "Listing directories in $subdir"
+            pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
+        else
+            __ybm_debug "Listing directories in ."
+            _filedir -d
+        fi
+    else
+        __ybm_handle_completion_types
+    fi
+
+    __ybm_handle_special_char "$cur" :
+    __ybm_handle_special_char "$cur" =
+
+    # Print the activeHelp statements before we finish
+    if [ ${#activeHelp[*]} -ne 0 ]; then
+        printf "\n";
+        printf "%s\n" "${activeHelp[@]}"
+        printf "\n"
+
+        # The prompt format is only available from bash 4.4.
+        # We test if it is available before using it.
+        if (x=${PS1@P}) 2> /dev/null; then
+            printf "%s" "${PS1@P}${COMP_LINE[@]}"
+        else
+            # Can't print the prompt.  Just print the
+            # text the user had typed, it is workable enough.
+            printf "%s" "${COMP_LINE[@]}"
+        fi
+    fi
+}
+
+# Separate activeHelp lines from real completions.
+# Fills the $activeHelp and $completions arrays.
+__ybm_extract_activeHelp() {
+    local activeHelpMarker="_activeHelp_ "
+    local endIndex=${#activeHelpMarker}
+
+    while IFS='' read -r comp; do
+        if [ "${comp:0:endIndex}" = "$activeHelpMarker" ]; then
+            comp=${comp:endIndex}
+            __ybm_debug "ActiveHelp found: $comp"
+            if [ -n "$comp" ]; then
+                activeHelp+=("$comp")
+            fi
+        else
+            # Not an activeHelp line but a normal completion
+            completions+=("$comp")
+        fi
+    done < <(printf "%s\n" "${out}")
+}
+
+__ybm_handle_completion_types() {
+    __ybm_debug "__ybm_handle_completion_types: COMP_TYPE is $COMP_TYPE"
+
+    case $COMP_TYPE in
+    37|42)
+        # Type: menu-complete/menu-complete-backward and insert-completions
+        # If the user requested inserting one completion at a time, or all
+        # completions at once on the command-line we must remove the descriptions.
+        # https://github.com/spf13/cobra/issues/1508
+        local tab=$'\t' comp
+        while IFS='' read -r comp; do
+            [[ -z $comp ]] && continue
+            # Strip any description
+            comp=${comp%%$tab*}
+            # Only consider the completions that match
+            if [[ $comp == "$cur"* ]]; then
+                COMPREPLY+=("$comp")
+            fi
+        done < <(printf "%s\n" "${completions[@]}")
+        ;;
+
+    *)
+        # Type: complete (normal completion)
+        __ybm_handle_standard_completion_case
+        ;;
+    esac
+}
+
+__ybm_handle_standard_completion_case() {
+    local tab=$'\t' comp
+
+    # Short circuit to optimize if we don't have descriptions
+    if [[ "${completions[*]}" != *$tab* ]]; then
+        IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
+        return 0
+    fi
+
+    local longest=0
+    local compline
+    # Look for the longest completion so that we can format things nicely
+    while IFS='' read -r compline; do
+        [[ -z $compline ]] && continue
+        # Strip any description before checking the length
+        comp=${compline%%$tab*}
+        # Only consider the completions that match
+        [[ $comp == "$cur"* ]] || continue
+        COMPREPLY+=("$compline")
+        if ((${#comp}>longest)); then
+            longest=${#comp}
+        fi
+    done < <(printf "%s\n" "${completions[@]}")
+
+    # If there is a single completion left, remove the description text
+    if [ ${#COMPREPLY[*]} -eq 1 ]; then
+        __ybm_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
+        comp="${COMPREPLY[0]%%$tab*}"
+        __ybm_debug "Removed description from single completion, which is now: ${comp}"
+        COMPREPLY[0]=$comp
+    else # Format the descriptions
+        __ybm_format_comp_descriptions $longest
+    fi
+}
+
+__ybm_handle_special_char()
+{
+    local comp="$1"
+    local char=$2
+    if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
+        local word=${comp%"${comp##*${char}}"}
+        local idx=${#COMPREPLY[*]}
+        while [[ $((--idx)) -ge 0 ]]; do
+            COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
+        done
+    fi
+}
+
+__ybm_format_comp_descriptions()
+{
+    local tab=$'\t'
+    local comp desc maxdesclength
+    local longest=$1
+
+    local i ci
+    for ci in ${!COMPREPLY[*]}; do
+        comp=${COMPREPLY[ci]}
+        # Properly format the description string which follows a tab character if there is one
+        if [[ "$comp" == *$tab* ]]; then
+            __ybm_debug "Original comp: $comp"
+            desc=${comp#*$tab}
+            comp=${comp%%$tab*}
+
+            # $COLUMNS stores the current shell width.
+            # Remove an extra 4 because we add 2 spaces and 2 parentheses.
+            maxdesclength=$(( COLUMNS - longest - 4 ))
+
+            # Make sure we can fit a description of at least 8 characters
+            # if we are to align the descriptions.
+            if [[ $maxdesclength -gt 8 ]]; then
+                # Add the proper number of spaces to align the descriptions
+                for ((i = ${#comp} ; i < longest ; i++)); do
+                    comp+=" "
+                done
+            else
+                # Don't pad the descriptions so we can fit more text after the completion
+                maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
+            fi
+
+            # If there is enough space for any description text,
+            # truncate the descriptions that are too long for the shell width
+            if [ $maxdesclength -gt 0 ]; then
+                if [ ${#desc} -gt $maxdesclength ]; then
+                    desc=${desc:0:$(( maxdesclength - 1 ))}
+                    desc+="…"
+                fi
+                comp+="  ($desc)"
+            fi
+            COMPREPLY[ci]=$comp
+            __ybm_debug "Final comp: $comp"
+        fi
+    done
+}
+
+__start_ybm()
+{
+    local cur prev words cword split
+
+    COMPREPLY=()
+
+    # Call _init_completion from the bash-completion package
+    # to prepare the arguments properly
+    if declare -F _init_completion >/dev/null 2>&1; then
+        _init_completion -n "=:" || return
+    else
+        __ybm_init_completion -n "=:" || return
+    fi
+
+    __ybm_debug
+    __ybm_debug "========= starting completion logic =========="
+    __ybm_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
+
+    # The user could have moved the cursor backwards on the command-line.
+    # We need to trigger completion from the $cword location, so we need
+    # to truncate the command-line ($words) up to the $cword location.
+    words=("${words[@]:0:$cword+1}")
+    __ybm_debug "Truncated words[*]: ${words[*]},"
+
+    local out directive
+    __ybm_get_completion_results
+    __ybm_process_completion_results
+}
+
+if [[ $(type -t compopt) = "builtin" ]]; then
+    complete -o default -F __start_ybm ybm
+else
+    complete -o default -o nospace -F __start_ybm ybm
+fi
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/completions/ybm.fish b/completions/ybm.fish
new file mode 100644
index 00000000..f76024e4
--- /dev/null
+++ b/completions/ybm.fish
@@ -0,0 +1,177 @@
+# fish completion for ybm                                  -*- shell-script -*-
+
+function __ybm_debug
+    set -l file "$BASH_COMP_DEBUG_FILE"
+    if test -n "$file"
+        echo "$argv" >> $file
+    end
+end
+
+function __ybm_perform_completion
+    __ybm_debug "Starting __ybm_perform_completion"
+
+    # Extract all args except the last one
+    set -l args (commandline -opc)
+    # Extract the last arg and escape it in case it is a space
+    set -l lastArg (string escape -- (commandline -ct))
+
+    __ybm_debug "args: $args"
+    __ybm_debug "last arg: $lastArg"
+
+    # Disable ActiveHelp which is not supported for fish shell
+    set -l requestComp "YBM_ACTIVE_HELP=0 $args[1] __complete $args[2..-1] $lastArg"
+
+    __ybm_debug "Calling $requestComp"
+    set -l results (eval $requestComp 2> /dev/null)
+
+    # Some programs may output extra empty lines after the directive.
+    # Let's ignore them or else it will break completion.
+    # Ref: https://github.com/spf13/cobra/issues/1279
+    for line in $results[-1..1]
+        if test (string trim -- $line) = ""
+            # Found an empty line, remove it
+            set results $results[1..-2]
+        else
+            # Found non-empty line, we have our proper output
+            break
+        end
+    end
+
+    set -l comps $results[1..-2]
+    set -l directiveLine $results[-1]
+
+    # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
+    # completions must be prefixed with the flag
+    set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
+
+    __ybm_debug "Comps: $comps"
+    __ybm_debug "DirectiveLine: $directiveLine"
+    __ybm_debug "flagPrefix: $flagPrefix"
+
+    for comp in $comps
+        printf "%s%s\n" "$flagPrefix" "$comp"
+    end
+
+    printf "%s\n" "$directiveLine"
+end
+
+# This function does two things:
+# - Obtain the completions and store them in the global __ybm_comp_results
+# - Return false if file completion should be performed
+function __ybm_prepare_completions
+    __ybm_debug ""
+    __ybm_debug "========= starting completion logic =========="
+
+    # Start fresh
+    set --erase __ybm_comp_results
+
+    set -l results (__ybm_perform_completion)
+    __ybm_debug "Completion results: $results"
+
+    if test -z "$results"
+        __ybm_debug "No completion, probably due to a failure"
+        # Might as well do file completion, in case it helps
+        return 1
+    end
+
+    set -l directive (string sub --start 2 $results[-1])
+    set --global __ybm_comp_results $results[1..-2]
+
+    __ybm_debug "Completions are: $__ybm_comp_results"
+    __ybm_debug "Directive is: $directive"
+
+    set -l shellCompDirectiveError 1
+    set -l shellCompDirectiveNoSpace 2
+    set -l shellCompDirectiveNoFileComp 4
+    set -l shellCompDirectiveFilterFileExt 8
+    set -l shellCompDirectiveFilterDirs 16
+
+    if test -z "$directive"
+        set directive 0
+    end
+
+    set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) % 2)
+    if test $compErr -eq 1
+        __ybm_debug "Received error directive: aborting."
+        # Might as well do file completion, in case it helps
+        return 1
+    end
+
+    set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) % 2)
+    set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)
+    if test $filefilter -eq 1; or test $dirfilter -eq 1
+        __ybm_debug "File extension filtering or directory filtering not supported"
+        # Do full file completion instead
+        return 1
+    end
+
+    set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) % 2)
+    set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) % 2)
+
+    __ybm_debug "nospace: $nospace, nofiles: $nofiles"
+
+    # If we want to prevent a space, or if file completion is NOT disabled,
+    # we need to count the number of valid completions.
+    # To do so, we will filter on prefix as the completions we have received
+    # may not already be filtered so as to allow fish to match on different
+    # criteria than the prefix.
+    if test $nospace -ne 0; or test $nofiles -eq 0
+        set -l prefix (commandline -t | string escape --style=regex)
+        __ybm_debug "prefix: $prefix"
+
+        set -l completions (string match -r -- "^$prefix.*" $__ybm_comp_results)
+        set --global __ybm_comp_results $completions
+        __ybm_debug "Filtered completions are: $__ybm_comp_results"
+
+        # Important not to quote the variable for count to work
+        set -l numComps (count $__ybm_comp_results)
+        __ybm_debug "numComps: $numComps"
+
+        if test $numComps -eq 1; and test $nospace -ne 0
+            # We must first split on \t to get rid of the descriptions to be
+            # able to check what the actual completion will be.
+            # We don't need descriptions anyway since there is only a single
+            # real completion which the shell will expand immediately.
+            set -l split (string split --max 1 \t $__ybm_comp_results[1])
+
+            # Fish won't add a space if the completion ends with any
+            # of the following characters: @=/:.,
+            set -l lastChar (string sub -s -1 -- $split)
+            if not string match -r -q "[@=/:.,]" -- "$lastChar"
+                # In other cases, to support the "nospace" directive we trick the shell
+                # by outputting an extra, longer completion.
+                __ybm_debug "Adding second completion to perform nospace directive"
+                set --global __ybm_comp_results $split[1] $split[1].
+                __ybm_debug "Completions are now: $__ybm_comp_results"
+            end
+        end
+
+        if test $numComps -eq 0; and test $nofiles -eq 0
+            # To be consistent with bash and zsh, we only trigger file
+            # completion when there are no other completions
+            __ybm_debug "Requesting file completion"
+            return 1
+        end
+    end
+
+    return 0
+end
+
+# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
+# so we can properly delete any completions provided by another script.
+# Only do this if the program can be found, or else fish may print some errors; besides,
+# the existing completions will only be loaded if the program can be found.
+if type -q "ybm"
+    # The space after the program name is essential to trigger completion for the program
+    # and not completion of the program name itself.
+    # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
+    complete --do-complete "ybm " > /dev/null 2>&1
+end
+
+# Remove any pre-existing completions for the program since we will be handling all of them.
+complete -c ybm -e
+
+# The call to __ybm_prepare_completions will setup __ybm_comp_results
+# which provides the program's completion choices.
+complete -c ybm -n '__ybm_prepare_completions' -f -a '$__ybm_comp_results'
+
diff --git a/completions/ybm.zsh b/completions/ybm.zsh
new file mode 100644
index 00000000..b22646cb
--- /dev/null
+++ b/completions/ybm.zsh
@@ -0,0 +1,205 @@
+#compdef ybm
+
+# zsh completion for ybm                                  -*- shell-script -*-
+
+__ybm_debug()
+{
+    local file="$BASH_COMP_DEBUG_FILE"
+    if [[ -n ${file} ]]; then
+        echo "$*" >> "${file}"
+    fi
+}
+
+_ybm()
+{
+    local shellCompDirectiveError=1
+    local shellCompDirectiveNoSpace=2
+    local shellCompDirectiveNoFileComp=4
+    local shellCompDirectiveFilterFileExt=8
+    local shellCompDirectiveFilterDirs=16
+
+    local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace
+    local -a completions
+
+    __ybm_debug "\n========= starting completion logic =========="
+    __ybm_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
+
+    # The user could have moved the cursor backwards on the command-line.
+    # We need to trigger completion from the $CURRENT location, so we need
+    # to truncate the command-line ($words) up to the $CURRENT location.
+    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
+    words=("${=words[1,CURRENT]}")
+    __ybm_debug "Truncated words[*]: ${words[*]},"
+
+    lastParam=${words[-1]}
+    lastChar=${lastParam[-1]}
+    __ybm_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
+
+    # For zsh, when completing a flag with an = (e.g., ybm -n=<TAB>)
+    # completions must be prefixed with the flag
+    setopt local_options BASH_REMATCH
+    if [[ "${lastParam}" =~ '-.*=' ]]; then
+        # We are dealing with a flag with an =
+        flagPrefix="-P ${BASH_REMATCH}"
+    fi
+
+    # Prepare the command to obtain completions
+    requestComp="${words[1]} __complete ${words[2,-1]}"
+    if [ "${lastChar}" = "" ]; then
+        # If the last parameter is complete (there is a space following it)
+        # We add an extra empty parameter so we can indicate this to the go completion code.
+        __ybm_debug "Adding extra empty parameter"
+        requestComp="${requestComp} \"\""
+    fi
+
+    __ybm_debug "About to call: eval ${requestComp}"
+
+    # Use eval to handle any environment variables and such
+    out=$(eval ${requestComp} 2>/dev/null)
+    __ybm_debug "completion output: ${out}"
+
+    # Extract the directive integer following a : from the last line
+    local lastLine
+    while IFS='\n' read -r line; do
+        lastLine=${line}
+    done < <(printf "%s\n" "${out[@]}")
+    __ybm_debug "last line: ${lastLine}"
+
+    if [ "${lastLine[1]}" = : ]; then
+        directive=${lastLine[2,-1]}
+        # Remove the directive including the : and the newline
+        local suffix
+        (( suffix=${#lastLine}+2))
+        out=${out[1,-$suffix]}
+    else
+        # There is no directive specified.  Leave $out as is.
+        __ybm_debug "No directive found.  Setting do default"
+        directive=0
+    fi
+
+    __ybm_debug "directive: ${directive}"
+    __ybm_debug "completions: ${out}"
+    __ybm_debug "flagPrefix: ${flagPrefix}"
+
+    if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
+        __ybm_debug "Completion received error. Ignoring completions."
+        return
+    fi
+
+    local activeHelpMarker="_activeHelp_ "
+    local endIndex=${#activeHelpMarker}
+    local startIndex=$((${#activeHelpMarker}+1))
+    local hasActiveHelp=0
+    while IFS='\n' read -r comp; do
+        # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker)
+        if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then
+            __ybm_debug "ActiveHelp found: $comp"
+            comp="${comp[$startIndex,-1]}"
+            if [ -n "$comp" ]; then
+                compadd -x "${comp}"
+                __ybm_debug "ActiveHelp will need delimiter"
+                hasActiveHelp=1
+            fi
+
+            continue
+        fi
+
+        if [ -n "$comp" ]; then
+            # If requested, completions are returned with a description.
+            # The description is preceded by a TAB character.
+            # For zsh's _describe, we need to use a : instead of a TAB.
+            # We first need to escape any : as part of the completion itself.
+            comp=${comp//:/\\:}
+
+            local tab="$(printf '\t')"
+            comp=${comp//$tab/:}
+
+            __ybm_debug "Adding completion: ${comp}"
+            completions+=${comp}
+            lastComp=$comp
+        fi
+    done < <(printf "%s\n" "${out[@]}")
+
+    # Add a delimiter after the activeHelp statements, but only if:
+    # - there are completions following the activeHelp statements, or
+    # - file completion will be performed (so there will be choices after the activeHelp)
+    if [ $hasActiveHelp -eq 1 ]; then
+        if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
+            __ybm_debug "Adding activeHelp delimiter"
+            compadd -x "--"
+            hasActiveHelp=0
+        fi
+    fi
+
+    if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
+        __ybm_debug "Activating nospace."
+        noSpace="-S ''"
+    fi
+
+    if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
+        # File extension filtering
+        local filteringCmd
+        filteringCmd='_files'
+        for filter in ${completions[@]}; do
+            if [ ${filter[1]} != '*' ]; then
+                # zsh requires a glob pattern to do file filtering
+                filter="\*.$filter"
+            fi
+            filteringCmd+=" -g $filter"
+        done
+        filteringCmd+=" ${flagPrefix}"
+
+        __ybm_debug "File filtering command: $filteringCmd"
+        _arguments '*:filename:'"$filteringCmd"
+    elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
+        # File completion for directories only
+        local subdir
+        subdir="${completions[1]}"
+        if [ -n "$subdir" ]; then
+            __ybm_debug "Listing directories in $subdir"
+            pushd "${subdir}" >/dev/null 2>&1
+        else
+            __ybm_debug "Listing directories in ."
+        fi
+
+        local result
+        _arguments '*:dirname:_files -/'" ${flagPrefix}"
+        result=$?
+        if [ -n "$subdir" ]; then
+            popd >/dev/null 2>&1
+        fi
+        return $result
+    else
+        __ybm_debug "Calling _describe"
+        if eval _describe "completions" completions $flagPrefix $noSpace; then
+            __ybm_debug "_describe found some completions"
+
+            # Return the success of having called _describe
+            return 0
+        else
+            __ybm_debug "_describe did not find completions."
+            __ybm_debug "Checking if we should do file completion."
+            if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
+                __ybm_debug "deactivating file completion"
+
+                # We must return an error code here to let zsh know that there were no
+                # completions found by _describe; this is what will trigger other
+                # matching algorithms to attempt to find completions.
+                # For example zsh can match letters in the middle of words.
+                return 1
+            else
+                # Perform file completion
+                __ybm_debug "Activating file completion"
+
+                # We must return the result of this command, so it must be the
+                # last command, or else we must store its result to return it.
+                _arguments '*:filename:_files'" ${flagPrefix}"
+            fi
+        fi
+    fi
+}
+
+# don't run the completion function when being source-ed or eval-ed
+if [ "$funcstack[1]" = "_ybm" ]; then
+    _ybm
+fi
diff --git a/scripts/completions.sh b/scripts/completions.sh
new file mode 100755
index 00000000..e117b3cf
--- /dev/null
+++ b/scripts/completions.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+# scripts/completions.sh
+# From https://carlosbecker.com/posts/golang-completions-cobra/
+set -e
+rm -rf completions
+mkdir completions
+for sh in bash zsh fish; do
+	go run main.go completion "$sh" >"completions/ybm.$sh"
+done
\ No newline at end of file