#!/usr/bin/env bash # ============================================ # Private helpers # ============================================ function cmd::shell::_prompt() { local user host dir user=$(whoami) host=$(hostname -s) dir=$(basename "$PWD") printf "\033[1;32m%s@%s\033[0m:\033[0;36m%s\033[0m \033[1;34mwgctl\033[0m> " \ "$user" "$host" "$dir" } function cmd::shell::_is_wgctl_command() { local cmd="$1" # Check against known wgctl commands local known=( list add remove rm inspect block unblock rule group audit logs watch fw config qr rename keys ip preset service shell help ) local c for c in "${known[@]}"; do [[ "$c" == "$cmd" ]] && return 0 done return 1 } function cmd::shell::_handle_builtin() { local input="$1" local first="${input%% *}" case "$first" in cd) local dir="${input#cd }" [[ "$dir" == "$input" ]] && dir="$HOME" cd "$dir" 2>/dev/null || log::error "cd: $dir: No such file or directory" return 0 ;; cd*) eval "$input" ;; # eval preserves shell state for cd export|unset|source|.) eval "$input" return 0 ;; esac return 1 # not a builtin } function cmd::shell::_execute() { local input="$1" local first="${input%% *}" local rest="${input#"$first"}" rest="${rest# }" # Handle shell builtins first cmd::shell::_handle_builtin "$input" && return 0 # Try as wgctl command via dispatcher if cmd::shell::_is_wgctl_command "$first"; then if [[ -n "$rest" ]]; then wgctl::dispatch "$first" $rest || true # never exit REPL on failure else wgctl::dispatch "$first" || true fi return 0 # Always 0 to keep REPL running fi # Fall back to bash bash -c "$input" || true # same for bash commands } function cmd::shell::_setup_history() { HISTFILE="${HOME}/.wgctl_history" HISTSIZE=1000 HISTFILESIZE=2000 history -r 2>/dev/null || true } function cmd::shell::_save_history() { history -w 2>/dev/null || true } function cmd::shell::_banner() { ui::section "wgctl shell" printf "\n" printf " Type wgctl commands directly, or any bash command.\n" printf " Type \033[1mexit\033[0m or \033[1mquit\033[0m to leave.\n" printf " Type \033[1mhelp\033[0m for wgctl commands.\n" printf "\n" } # ============================================ # Run # ============================================ function cmd::shell::on_load() { : # no flags needed } function cmd::shell::help() { cat < list wgctl> inspect --name phone-nuno wgctl> ls /etc/wireguard wgctl> exit EOF } function cmd::shell::run() { cmd::shell::_banner cmd::shell::_setup_history while true; do local input # Read with readline support (-e) and custom prompt IFS= read -r -e -p "$(cmd::shell::_prompt)" input || break # Handle empty input [[ -z "${input// }" ]] && continue # Add to history history -s "$input" # Handle exit case "${input%% *}" in exit|quit) break ;; esac # Execute cmd::shell::_execute "$input" done cmd::shell::_save_history printf "\n Goodbye!\n\n" } # ============================================ # Tab completion (loaded when shell starts) # ============================================ function cmd::shell::_setup_completion() { local commands="list add remove inspect block unblock rule group audit logs watch fw config qr rename shell help" function _wgctl_shell_complete() { local cur="${COMP_WORDS[COMP_CWORD]}" COMPREPLY=( $(compgen -W "$commands" -- "$cur") ) } # Bind completion to the read prompt bind 'set show-all-if-ambiguous on' 2>/dev/null || true bind 'set completion-ignore-case on' 2>/dev/null || true }