From 6a70dd2b2b6ff526cc8cfaa1db288868646e1a7d Mon Sep 17 00:00:00 2001 From: ducoterra Date: Wed, 28 Oct 2026 00:50:56 +0000 Subject: [PATCH] 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 --- contrib/ai-sandbox.bash | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 contrib/ai-sandbox.bash diff --git a/contrib/ai-sandbox.bash b/contrib/ai-sandbox.bash new file mode 100755 index 0000000..1717cb5 --- /dev/null +++ b/contrib/ai-sandbox.bash @@ -0,0 +1,81 @@ +# 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