Files
ai-sandbox/contrib/ai-sandbox.bash
T
ducoterra 6a70dd2b2b add bash completion, flags mirrored from parse_args
Typing `ai-sandbox --` gave nothing, and a policy surface this wide
(egress modes, mounts, the agents share, the pi bridge, per-subcommand
flags) is not something to retype from memory. Nothing in the script or
install.sh emitted completions, so this adds one as a standalone file
rather than growing the script.

It mirrors parse_args() plus the per-subcommand parsers, and follows the
same rules the script does: a non-flag word that is not a subcommand ends
sandbox parsing, so after it the completer offers nothing rather than
flags that would silently become harness arguments; build, update, doctor
and clean only offer the flags they actually accept, since each dies on
an unknown one; inspect takes the full policy set, so it gets the full
list. Values complete from local state only -- --net and --harness from
their fixed modes, --env from exported host variables, --mount and
--workdir from directories, --allow from the two documented allow files.
Nothing here resolves a name or touches the network. Aliases that point
at the script (alias pi=ai-sandbox) register too, which is why the source
line belongs after the alias in ~/.bashrc.

Verified by driving COMP_WORDS for each case above and diffing the flag
list against parse_args(); bash -n is clean. Note the Makefile lint list
does not yet include contrib/, so shellcheck has not seen this file.

LLM-Generated: true
2026-10-28 00:50:56 +00:00

82 lines
4.2 KiB
Bash
Executable File

# bash completion for ai-sandbox.
#
# Install (either):
# mkdir -p ~/.local/share/bash-completion/completions
# cp contrib/ai-sandbox.bash ~/.local/share/bash-completion/completions/ai-sandbox
# or source it from ~/.bashrc -- put the `source` line *after* the alias, since
# bash needs `complete` registered under the alias name (`alias pi=ai-sandbox`).
#
# The flag list mirrors parse_args() in bin/ai-sandbox -- keep them in sync.
_ai_sandbox_completions() {
local cur prev word first="" sub="" i
if type _get_comp_words_by_ref >/dev/null 2>&1; then
_get_comp_words_by_ref -n : cur prev
else
cur="${COMP_WORDS[COMP_CWORD]}"; prev="${COMP_WORDS[COMP_CWORD-1]:-}"
fi
local subs="build update doctor gate ps clean inspect version help"
# flags that consume the next word, so their values are not mistaken for
# positional (harness) arguments.
local value_flags=" --net --allow --allow-file --proxy --mount --workdir --agents-dir
--env --env-file --base-image --install --harness --pi-token-env --cpus --memory
--pids --tmpfs-size --name --label "
# Walk the words before the cursor to find the subcommand, skipping option values.
local skip=0
for ((i = 1; i < COMP_CWORD; i++)); do
word="${COMP_WORDS[i]}"
if (( skip )); then skip=0; continue; fi
if [[ "$word" == --* && "$value_flags" == *" $word "* ]]; then skip=1; continue; fi
[[ "$word" == -* ]] && continue
[[ -z "$first" ]] && first="$word"
done
[[ " $subs " == *" $first "* ]] && sub="$first"
case "$prev" in # values of the flag just typed
--net) COMPREPLY=( $(compgen -W "none allow full" -- "$cur") ); return ;;
--harness) COMPREPLY=( $(compgen -W "pi codex claude none" -- "$cur") ); return ;;
--env) COMPREPLY=( $(compgen -A export -- "$cur") ); return ;;
--allow) # hosts already in the allow files; no DNS, no network
local hosts
hosts="$(sed 's/#.*//' "$HOME/.config/ai-sandbox/allow" ./.ai-sandbox/allow 2>/dev/null | tr ',[:space:]' '\n\n')"
COMPREPLY=( $(compgen -W "$hosts" -- "$cur") )
(( ${#COMPREPLY[@]} )) || COMPREPLY=( "$cur" ) # don't fall back to filenames
return ;;
--proxy) COMPREPLY=( "$cur" ); return ;;
--allow-file|--env-file) COMPREPLY=( $(compgen -f -- "$cur") ); return ;;
--mount|--agents-dir|--workdir) COMPREPLY=( $(compgen -d -- "$cur") ); return ;;
esac
# A non-flag word before the cursor that is not a subcommand means the rest goes
# to the harness: ai-sandbox stops parsing there, so offer nothing, not sandbox flags.
[[ -n "$first" && -z "$sub" ]] && return 1
[[ -z "$first" && "$cur" != -* ]] && { COMPREPLY=( $(compgen -W "$subs" -- "$cur") ); return; }
# subcommand-specific flags (each subcommand dies on anything else);
# `inspect` takes the full policy flag set, so it falls through to the list below.
case "$sub" in
build) COMPREPLY=( $(compgen -W "--rebuild -f --refresh-base -B --gate" -- "$cur") ); return ;;
update) COMPREPLY=( $(compgen -W "--check --no-gate" -- "$cur") ); return ;;
doctor) COMPREPLY=( $(compgen -W "--deep -d" -- "$cur") ); return ;;
clean) COMPREPLY=( $(compgen -W "--all -a --volumes -V" -- "$cur") ); return ;;
gate) COMPREPLY=( $(compgen -W "up down status logs" -- "$cur") ); return ;;
ps|version) return 1 ;;
esac
COMPREPLY=( $(compgen -W \
"--net --allow --allow-file --proxy --no-gate --mount --no-cwd --workdir --home-cache --ephemeral-home --read-only --agents --no-agents --agents-dir --env --env-file --base-image --install --harness --pi-token-env --no-pi-token-env --no-pi-config --cpus --memory --pids --tmpfs-size --shell --interactive --no-tty --keep --name --label --no-build --dry-run --print-policy --print-entrypoint --print-containerfile --allow-unenforced --insecure-keep-root --strict-args -h --help -V" \
-- "$cur") )
}
complete -o default -F _ai_sandbox_completions ai-sandbox 2>/dev/null || true
if [[ $- == *i* ]]; then # also complete the harness alias
for _aisb in $(compgen -A alias 2>/dev/null); do
if alias "$_aisb" 2>/dev/null | grep -q 'ai-sandbox'; then
complete -o default -F _ai_sandbox_completions "$_aisb" 2>/dev/null || true
fi
done
unset _aisb
fi