From 300b742ac887a9b51f5103c787a1399207cb2b42 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 27 Aug 2026 03:27:09 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20alias=20trust=20flag=20=E2=80=94=20auto?= =?UTF-8?q?-execute=20agent=20commands=20without=20confirmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional5th 'trusted' field to aliases (name|provider|session|prompt|trusted). Trusted aliases pass --trust to pos ai, which makes _prompt_run_command auto-execute the agent's detected commands without the Y/n confirmation (command still printed for audit). - bin/pos-ai: new --trust global flag; _prompt_run_command takes trusted arg and skips the prompt when set; POS_FLAGS + usage updated - bin/pos-ai-alias: _ALIAS_TRUSTED array, 5-field env format (backward compat: missing field defaults to untrusted), Trust column in table, trust row in show, trust step (5/5) in create wizard with security warning, trust toggle (4/4) with diff tag in edit wizard, wrapper scripts get --trust when alias is trusted - completions/pos.bash + gen docs updated Gates: make gen && make check && make lint = 0 FAIL, 0 WARN --- DOC/AGENT_Context_Project.md | 4 +- bin/pos-ai | 22 +++++++-- bin/pos-ai-alias | 96 +++++++++++++++++++++++++++--------- completions/pos.bash | 2 +- 4 files changed, 93 insertions(+), 31 deletions(-) diff --git a/DOC/AGENT_Context_Project.md b/DOC/AGENT_Context_Project.md index 18c8c14..b5891a5 100644 --- a/DOC/AGENT_Context_Project.md +++ b/DOC/AGENT_Context_Project.md @@ -603,7 +603,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `features/usb-automount.sh` | 138 | USB automount feature (udev rule + flag-gated service) | | `bin/pos` | 302 | CLI dispatcher with smart arg matching + logging + category help | -| `bin/pos-ai-alias` | 658 | manage AI agent aliases | +| `bin/pos-ai-alias` | 712 | manage AI agent aliases | | `bin/pos-ai-gemini` | 7 | Forward to pos ai --provider gemini (backward compat) | | `bin/pos-ai-openrouter` | 7 | Forward to pos ai --provider openrouter (backward compat) | | `bin/pos-communication-matrix-listener` | 568 | Matrix listener: map /command → bash, run them on room messages | @@ -641,7 +641,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `bin/pos-system-health` | 209 | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL | | `bin/pos-system-schedule` | 151 | Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently | | `bin/pos-system-uninstall` | 435 | Remove pos toolkit binaries, services, shell integration, config, and data | -| `bin/pos-ai` | 680 | AI assistant: ask, chat, sessions, capture, models, providers | +| `bin/pos-ai` | 692 | AI assistant: ask, chat, sessions, capture, models, providers | | `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) | | `bin/pos-tree` | 118 | Show the pos CLI command tree: categories, commands, and subcommands | | `completions/pos.bash` | 307 | Dynamic bash completion | diff --git a/bin/pos-ai b/bin/pos-ai index b853285..df3949a 100755 --- a/bin/pos-ai +++ b/bin/pos-ai @@ -2,7 +2,7 @@ set -euo pipefail # POS: ai ask — AI assistant: ask, chat, sessions, capture, models, providers # POS_SUBCMDS: ask chat sessions capture models providers -# POS_FLAGS: --provider --model --session --system --full --last +# POS_FLAGS: --provider --model --session --system --full --last --trust # POS_CONFIG: ai | ai.env | AI_PROVIDER=:Provider (gemini or openrouter, default gemini) | @[AI_PROVIDER=gemini|] Gemini | *providers=gemini | @[AI_PROVIDER=openrouter] OpenRouter | *providers=openrouter | @General | AI_SYSTEM_PROMPT=:Custom system prompt (overrides built-in, empty to reset) source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" @@ -37,7 +37,7 @@ LEGACY_OPENROUTER_CONFIG="$HOME/.config/linux_post_install/ai-openrouter.env" usage() { cat <] [--model ] [--session ] [--system ] [--full] [--last] +Usage: pos ai [subcommand] [--provider ] [--model ] [--session ] [--system ] [--full] [--last] [--trust] AI assistant with pluggable providers (gemini, openrouter). @@ -72,6 +72,9 @@ Options: order: (1) newest pos log, (2) captured output from 'capture'. Notes on stderr which source was attached and its age; warns when stale (>60 min). + --trust Auto-execute agent-detected commands without confirmation. + Used by trusted alias wrappers — do NOT pass manually + unless you fully trust the agent's output. -h|--help This help. Config: $CONFIG_FILE (edit with 'pos config ai') @@ -371,11 +374,17 @@ _extract_commands() { # ── Interactive prompt to run extracted commands ───────────────── _prompt_run_command() { - local cmd="$1" + local cmd="$1" trusted="${2:-0}" # Only prompt on interactive terminals with a controlling tty [ -w /dev/tty ] || return 0 printf '\n%s\n' "Command detected:" >&2 printf ' %s\n\n' "$cmd" >&2 + if [ "$trusted" -eq 1 ]; then + printf '[trusted] Auto-executing (no confirmation)\n\n' >&2 + printf '%s\n' "$cmd" + run eval "$cmd" + return + fi printf 'Run this command? [Y/n] ' >&2 local choice IFS= read -r choice "$ENV_FILE" chmod 600 "$ENV_FILE" @@ -56,9 +58,10 @@ _alias_save() { # ONE shell word (printf %q) — shared by regen (stored form) and show # (copy-pasteable display form). Empty prompt → no --system fragment. _alias_quote_cmd() { - local provider="$1" session="$2" prompt="${3:-}" q_prompt + local provider="$1" session="$2" prompt="${3:-}" trusted="${4:-0}" q_prompt printf -v q_prompt '%q' "$prompt" printf 'pos ai %s ask --session %s' "$provider" "$session" + [ "$trusted" -eq 1 ] && printf ' --trust' [ -n "$prompt" ] && printf ' --system %s' "$q_prompt" return 0 } @@ -79,27 +82,27 @@ _alias_owned() { [ -f "$1" ] && sed -n '2p' "$1" 2>/dev/null | grep -q 'Managed by pos ai alias' } -# Render one wrapper to stdout (args: name provider session prompt). +# Render one wrapper to stdout (args: name provider session prompt [trusted]). # The exec line reuses _alias_quote_cmd's double-%q mechanics so the prompt # lands as exactly ONE shell word; "$@" passes user args through. _wrapper_render() { - local name="$1" provider="$2" session="$3" prompt="${4:-}" + local name="$1" provider="$2" session="$3" prompt="${4:-}" trusted="${5:-0}" cat <"$tmp" + _wrapper_render "$1" "$2" "$3" "$4" "${5:-0}" >"$tmp" if cmp -s "$tmp" "$path"; then rm -f "$tmp" return 0 @@ -156,7 +159,7 @@ _alias_sync() { mkdir -p "$bin_dir" for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do _wrapper_install "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \ - "${_ALIAS_SESSIONS[$i]}" "${_ALIAS_PROMPTS[$i]}" || : + "${_ALIAS_SESSIONS[$i]}" "${_ALIAS_PROMPTS[$i]}" "${_ALIAS_TRUSTED[$i]:-0}" || : done for f in "$bin_dir"/*; do [ -f "$f" ] || continue @@ -227,12 +230,14 @@ _alias_prompt_truncate() { _alias_table() { local count=${#_ALIAS_NAMES[@]} i [ "$count" -eq 0 ] && return 0 - printf ' %-12s %-12s %-12s %s\n' "Name" "Provider" "Session" "Prompt" - printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \ + printf ' %-12s %-12s %-12s %-5s %s\n' "Name" "Provider" "Session" "Trust" "Prompt" + printf ' %-12s %-12s %-12s %-5s %s\n' "------------" "------------" "------------" "-----" \ "------------------------------------------" for ((i = 0; i < count; i++)); do - printf ' %-12s %-12s %-12s %s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \ - "${_ALIAS_SESSIONS[$i]}" "$(_alias_prompt_truncate "${_ALIAS_PROMPTS[$i]}")" + local _tmark="—" + [ "${_ALIAS_TRUSTED[$i]:-0}" = "1" ] && _tmark="yes" + printf ' %-12s %-12s %-12s %-5s %s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \ + "${_ALIAS_SESSIONS[$i]}" "$_tmark" "$(_alias_prompt_truncate "${_ALIAS_PROMPTS[$i]}")" done } @@ -247,6 +252,7 @@ _alias_show() { [ "$idx" = "-1" ] && err "Alias '$1' not found" local name="${_ALIAS_NAMES[$idx]}" provider="${_ALIAS_PROVIDERS[$idx]}" local session="${_ALIAS_SESSIONS[$idx]}" prompt="${_ALIAS_PROMPTS[$idx]}" + local trusted="${_ALIAS_TRUSTED[$idx]:-0}" [ -z "$session" ] && session="$name" printf ' %-12s %s\n' "Alias:" "$name" printf ' %-12s %s\n' "Provider:" "$provider" @@ -257,9 +263,10 @@ _alias_show() { printf ' %-12s %s\n' "Wrapper:" "(not installed — ~/.local/bin not on PATH)" fi printf ' %-12s %s\n' "Prompt:" "${prompt:-$(printf '%s' "(default)")}" + printf ' %-12s %s\n' "Trusted:" "$([ "$trusted" = "1" ] && echo "yes (auto-executes commands)" || echo "no (prompts before running)")" # Show the resolved command (same quoting mechanism as the generated # wrapper — what users copy from here pastes into a shell verbatim) - printf ' %-12s %s\n' "Command:" "$(_alias_quote_cmd "$provider" "$session" "$prompt")" + printf ' %-12s %s\n' "Command:" "$(_alias_quote_cmd "$provider" "$session" "$prompt" "$trusted")" } # ── Interactive: main menu ───────────────────────────────────── @@ -346,7 +353,7 @@ _alias_create() { # Step 3: Session name local session="" while true; do - step 3 4 "Session Name" >&2 + step 3 5 "Session Name" >&2 session="$(menu_ask_value "Session name" "$name")" || return 0 if [ -n "$session" ] && ! _alias_name_valid "$session"; then warn "Invalid session '$session' — use letters, digits, hyphens, underscores" >&2 @@ -359,7 +366,7 @@ _alias_create() { # Step 4: System prompt local prompt="" while true; do - step 4 4 "System Prompt" >&2 + step 4 5 "System Prompt" >&2 prompt="$(menu_ask_value "System prompt (empty = use built-in)" "")" || return 0 if [[ "$prompt" == *'|'* ]]; then warn "System prompt must not contain '|' characters" >&2 @@ -371,6 +378,26 @@ _alias_create() { break done + # Step 5: Trust level + local trusted="0" + while true; do + step 5 5 "Trust Level" >&2 + { + echo " TRUSTED aliases auto-execute commands from the agent" + echo " WITHOUT asking for confirmation." + echo "" + echo " Only enable this for aliases you fully trust with" + echo " unrestricted shell access on this machine." + } >&2 + local trust_ans + trust_ans="$(menu_ask_value "Trust this alias? (y/N)" "N")" || return 0 + case "${trust_ans,,}" in + y|yes) trusted="1"; break ;; + n|no|"") trusted="0"; break ;; + *) warn "Please answer y or n" >&2 ;; + esac + done + # Confirmation { echo "────────────────────────────────────────────" @@ -380,6 +407,7 @@ _alias_create() { local dp="$prompt" [ ${#dp} -gt 50 ] && dp="${dp:0:50}…" printf ' Prompt: %s\n' "${dp:-}" + printf ' Trusted: %s\n' "$([ "$trusted" = "1" ] && echo "yes (auto-execute)" || echo "no (confirm before run)")" echo "────────────────────────────────────────────" } >&2 if ! confirm "Create alias '$name'?" y; then @@ -392,6 +420,7 @@ _alias_create() { _ALIAS_PROVIDERS+=("$provider") _ALIAS_SESSIONS+=("$session") _ALIAS_PROMPTS+=("$prompt") + _ALIAS_TRUSTED+=("$trusted") _alias_save _alias_sync log "Alias '$name' created." >&2 @@ -438,16 +467,18 @@ _alias_edit() { local cp="${_ALIAS_PROMPTS[$idx]}" [ -z "$cp" ] && cp="(default)" printf ' Prompt: %s\n' "$cp" + printf ' Trusted: %s\n' "$([ "${_ALIAS_TRUSTED[$idx]:-0}" = "1" ] && echo "yes" || echo "no")" echo >&2 } >&2 local new_provider="${_ALIAS_PROVIDERS[$idx]}" local new_session="${_ALIAS_SESSIONS[$idx]}" local new_prompt="${_ALIAS_PROMPTS[$idx]}" + local new_trusted="${_ALIAS_TRUSTED[$idx]:-0}" local changed=0 # Edit provider - step 1 3 "Provider" >&2 + step 1 4 "Provider" >&2 local pidx pidx="$(_alias_provider_pick)" || return 0 local providers=() @@ -464,7 +495,7 @@ _alias_edit() { # Edit session local tmp_session="" while true; do - step 2 3 "Session Name" >&2 + step 2 4 "Session Name" >&2 tmp_session="$(menu_ask_value "Session name" "$new_session")" || return 0 if [ -n "$tmp_session" ] && ! _alias_name_valid "$tmp_session"; then warn "Invalid session '$tmp_session' — use letters, digits, hyphens, underscores" >&2 @@ -478,7 +509,7 @@ _alias_edit() { # Edit prompt local tmp_prompt="" while true; do - step 3 3 "System Prompt" >&2 + step 3 4 "System Prompt" >&2 local default_prompt="${_ALIAS_PROMPTS[$idx]}" [ ${#default_prompt} -gt 80 ] && default_prompt="${default_prompt:0:80}…" [ -z "$default_prompt" ] && default_prompt="" @@ -498,6 +529,19 @@ _alias_edit() { fi [ "$new_prompt" != "${_ALIAS_PROMPTS[$idx]}" ] && changed=1 + # Edit trust + step 4 4 "Trust Level" >&2 + local cur_trust_label="no" + [ "$new_trusted" = "1" ] && cur_trust_label="yes" + local trust_ans + trust_ans="$(menu_ask_value "Trust this alias? (y/N)" "$cur_trust_label")" || return 0 + case "${trust_ans,,}" in + y|yes) new_trusted="1" ;; + n|no|"") new_trusted="$new_trusted" ;; + *) warn "Please answer y or n" >&2 ;; + esac + [ "$new_trusted" != "${_ALIAS_TRUSTED[$idx]:-0}" ] && changed=1 + # No changes? if [ "$changed" -eq 0 ]; then log "No changes — nothing to save." >&2 @@ -508,15 +552,17 @@ _alias_edit() { { echo "────────────────────────────────────────────" printf ' Save changes to '\''%s'\''?\n' "$name" - local tag_p tag_s tag_pr + local tag_p tag_s tag_pr tag_t [ "$new_provider" = "${_ALIAS_PROVIDERS[$idx]}" ] && tag_p="(unchanged)" || tag_p="(changed)" [ "$new_session" = "${_ALIAS_SESSIONS[$idx]}" ] && tag_s="(unchanged)" || tag_s="(changed)" [ "$new_prompt" = "${_ALIAS_PROMPTS[$idx]}" ] && tag_pr="(unchanged)" || tag_pr="(changed)" + [ "$new_trusted" = "${_ALIAS_TRUSTED[$idx]:-0}" ] && tag_t="(unchanged)" || tag_t="(changed)" printf ' Provider: %-12s %s\n' "$new_provider" "$tag_p" printf ' Session: %-12s %s\n' "$new_session" "$tag_s" local dp="$new_prompt" [ -z "$dp" ] && dp="" printf ' Prompt: %s %s\n' "${dp:0:40}" "$tag_pr" + printf ' Trusted: %-12s %s\n' "$([ "$new_trusted" = "1" ] && echo "yes" || echo "no")" "$tag_t" echo "────────────────────────────────────────────" } >&2 if ! confirm "Save changes to '$name'?" y; then @@ -528,6 +574,7 @@ _alias_edit() { _ALIAS_PROVIDERS[$idx]="$new_provider" _ALIAS_SESSIONS[$idx]="$new_session" _ALIAS_PROMPTS[$idx]="$new_prompt" + _ALIAS_TRUSTED[$idx]="$new_trusted" _alias_save _alias_sync log "Alias '$name' updated — the change is live on next invocation." >&2 @@ -610,8 +657,11 @@ usage() { Usage: pos ai alias [subcommand] [args] Manage named AI agent aliases — create, edit, remove, list, and show -configured aliases. Each alias maps a name to a provider, session, and -optional system prompt. +configured aliases. Each alias maps a name to a provider, session, +optional system prompt, and a trust level. + +Trusted aliases auto-execute the agent's commands without asking for +confirmation. Only enable for aliases you fully trust with shell access. Subcommands: (no args) Interactive menu diff --git a/completions/pos.bash b/completions/pos.bash index a65f729..c774cff 100644 --- a/completions/pos.bash +++ b/completions/pos.bash @@ -20,7 +20,7 @@ _pos_flags[share-usb-server]="--ls --ls-shared --share --unshare --auto-share -- _pos_flags[system-backup]="--service --no-encrypt" _pos_flags[system-schedule]="--dry-run" _pos_flags[system-uninstall]="--yes --config --data" -_pos_flags[ai]="--provider --model --session --system --full --last" +_pos_flags[ai]="--provider --model --session --system --full --last --trust" _pos_flags[tree]="--depth" # GEN:END posflags # GEN:START possubcmds