Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f289ba31b | |||
| a5c19e842d | |||
| d84a35efce | |||
| 9564880ebf | |||
| d0299d3f98 | |||
| c1f1c4109f | |||
| 710b626f47 | |||
| 1c19c0de59 | |||
| e0c9ba384a |
@@ -633,7 +633,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` | 415 | Remove pos toolkit binaries, services, shell integration, config, and data |
|
||||
| `bin/pos-ai` | 645 | AI assistant: ask, chat, sessions, capture, models, providers |
|
||||
| `bin/pos-ai` | 680 | 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` | 112 | Show the pos CLI command tree: categories, commands, and subcommands |
|
||||
| `completions/pos.bash` | 306 | Dynamic bash completion |
|
||||
|
||||
+51
-5
@@ -3,7 +3,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_CONFIG: ai | ai.env | AI_PROVIDER=:Provider (gemini or openrouter, default gemini) | AI_GEMINI_API_KEY=secret:Gemini API key from aistudio.google.com | OPENROUTER_API_KEY=secret:OpenRouter API key from openrouter.ai | AI_MODEL=:Model id (default per provider) | AI_SYSTEM_PROMPT=:Custom system prompt (overrides built-in, empty to reset)
|
||||
# POS_CONFIG: ai | ai.env | AI_PROVIDER=:Provider (gemini or openrouter, default gemini) | *providers | 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"
|
||||
|
||||
@@ -76,11 +76,9 @@ Options:
|
||||
|
||||
Config: $CONFIG_FILE (edit with 'pos config ai')
|
||||
AI_PROVIDER Provider to use (gemini|openrouter, default gemini)
|
||||
AI_GEMINI_API_KEY Gemini API key from aistudio.google.com
|
||||
OPENROUTER_API_KEY OpenRouter API key from openrouter.ai
|
||||
AI_MODEL Model id (default depends on provider)
|
||||
AI_SYSTEM_PROMPT Custom system prompt (overrides built-in; empty to reset)
|
||||
OPENROUTER_MODEL Legacy: OpenRouter model fallback
|
||||
Provider keys: auto-discovered from lib/ai-providers/*.sh
|
||||
(AI_GEMINI_API_KEY, OPENROUTER_API_KEY, etc.)
|
||||
|
||||
Notes:
|
||||
ask is terse by default: a built-in system instruction tells the model to
|
||||
@@ -355,6 +353,46 @@ render_markdown() {
|
||||
printf '\n%s\n' "$rendered"
|
||||
}
|
||||
|
||||
# ── Command extraction from AI responses ────────────────────────
|
||||
_extract_commands() {
|
||||
local text="$1"
|
||||
printf '%s' "$text" | awk '
|
||||
/^```(bash|sh|shell)/ { in_block=1; next }
|
||||
/^```/ { if (in_block) in_block=0; next }
|
||||
in_block && NF > 0 { lines[++n] = $0 }
|
||||
END {
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (i > 1) printf "\n"
|
||||
printf "%s", lines[i]
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
# ── Interactive prompt to run extracted commands ─────────────────
|
||||
_prompt_run_command() {
|
||||
local cmd="$1"
|
||||
# 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
|
||||
printf 'Run this command? [Y/n] ' >&2
|
||||
local choice
|
||||
IFS= read -r choice </dev/tty || choice=""
|
||||
case "${choice,,}" in
|
||||
n|N)
|
||||
# Add to shell history so user can press ↑ to recall, edit, run
|
||||
history -s "$cmd" 2>/dev/null || true
|
||||
printf '%s\n' "Command added to history — press ↑ to recall, edit, and run." >&2
|
||||
;;
|
||||
*)
|
||||
# Y or Enter: execute
|
||||
printf '%s\n' "$cmd"
|
||||
run eval "$cmd"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Machine context appended to the built-in default prompt ─────
|
||||
mc_clean() {
|
||||
sed -e 's/\x1b\[[0-9;]*[A-Za-z]//g' \
|
||||
@@ -473,6 +511,10 @@ cmd_ask() {
|
||||
messages="$(session_push "$messages" assistant "$out")"
|
||||
session_save "$messages"
|
||||
render_markdown "$out"
|
||||
# Command execution prompt: extract commands from response and offer to run
|
||||
local _cmd
|
||||
_cmd="$(_extract_commands "$out")"
|
||||
[ -n "$_cmd" ] && _prompt_run_command "$_cmd"
|
||||
}
|
||||
|
||||
cmd_chat() {
|
||||
@@ -506,6 +548,10 @@ cmd_chat() {
|
||||
session_save "$messages"
|
||||
printf '\n'
|
||||
render_markdown "$answer"
|
||||
# Command execution prompt: extract commands from response and offer to run
|
||||
local _cmd
|
||||
_cmd="$(_extract_commands "$answer")"
|
||||
[ -n "$_cmd" ] && _prompt_run_command "$_cmd"
|
||||
printf '\n\n'
|
||||
done
|
||||
echo
|
||||
|
||||
Executable
+542
@@ -0,0 +1,542 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: ai alias — manage AI agent aliases
|
||||
# POS_SUBCMDS: create edit remove list show
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"
|
||||
|
||||
# ── Paths & constants ──────────────────────────────────────────
|
||||
ENV_FILE="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}/ai-aliases.env"
|
||||
SH_FILE="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}/ai-aliases.sh"
|
||||
|
||||
# Provider discovery — same pattern as bin/pos-ai (lines 17-18)
|
||||
PROVIDER_DIR="$(dirname "$0")/../lib/ai-providers"
|
||||
[ -d "$PROVIDER_DIR" ] || PROVIDER_DIR="$(dirname "$0")/ai-providers"
|
||||
|
||||
# ── Core helpers ───────────────────────────────────────────────
|
||||
|
||||
_alias_load() {
|
||||
_ALIAS_NAMES=(); _ALIAS_PROVIDERS=(); _ALIAS_SESSIONS=(); _ALIAS_PROMPTS=()
|
||||
[ -f "$ENV_FILE" ] || return 0
|
||||
while IFS='|' read -r name provider session prompt _rest; do
|
||||
[[ "$name" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "${name// /}" ]] && continue
|
||||
name="${name## }"; name="${name%% }"
|
||||
[[ "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] || continue
|
||||
provider="${provider## }"; provider="${provider%% }"
|
||||
session="${session## }"; session="${session%% }"
|
||||
_ALIAS_NAMES+=("$name")
|
||||
_ALIAS_PROVIDERS+=("$provider")
|
||||
_ALIAS_SESSIONS+=("$session")
|
||||
_ALIAS_PROMPTS+=("$prompt")
|
||||
done < <(grep -v '^[[:space:]]*#' "$ENV_FILE" | grep -v '^[[:space:]]*$' || true)
|
||||
}
|
||||
|
||||
_alias_save() {
|
||||
mkdir -p "$(dirname "$ENV_FILE")"
|
||||
{
|
||||
printf '%s\n' "# AI aliases — managed by pos ai alias (do not hand-edit)"
|
||||
printf '%s\n' "# Format: alias_name|provider|session_name|system_prompt"
|
||||
printf '%s\n' "#"
|
||||
local i
|
||||
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
|
||||
printf '%s|%s|%s|%s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \
|
||||
"${_ALIAS_SESSIONS[$i]}" "${_ALIAS_PROMPTS[$i]}"
|
||||
done
|
||||
} >"$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
}
|
||||
|
||||
_alias_regen() {
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
printf '%s\n' "#!/usr/bin/env bash" >"$tmp"
|
||||
printf '%s\n\n' "# Auto-generated by pos ai alias — do not hand-edit." >>"$tmp"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
while IFS='|' read -r name provider session prompt _rest; do
|
||||
[[ "$name" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "${name// /}" ]] && continue
|
||||
name="${name## }"; name="${name%% }"
|
||||
[[ "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] || continue
|
||||
provider="${provider## }"; provider="${provider%% }"
|
||||
session="${session## }"; session="${session%% }"
|
||||
[ -z "$session" ] && session="$name"
|
||||
local escaped_prompt="${prompt//\'/\'\\\'\'}"
|
||||
printf "alias %s='pos ai %s ask --session %s" "$name" "$provider" "$session" >>"$tmp"
|
||||
if [ -n "$prompt" ]; then
|
||||
printf " --system '%s'" "$escaped_prompt" >>"$tmp"
|
||||
fi
|
||||
printf "'\n" >>"$tmp"
|
||||
done < <(grep -v '^[[:space:]]*#' "$ENV_FILE" | grep -v '^[[:space:]]*$' || true)
|
||||
fi
|
||||
if ! bash -n "$tmp" 2>/dev/null; then
|
||||
warn "Generated alias file has syntax errors — keeping previous version"
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
mv "$tmp" "$SH_FILE"
|
||||
chmod 644 "$SH_FILE"
|
||||
}
|
||||
|
||||
_alias_provider_pick() {
|
||||
local providers=()
|
||||
for f in "$PROVIDER_DIR"/*.sh; do
|
||||
[ -f "$f" ] || continue
|
||||
providers+=("$(basename "$f" .sh)")
|
||||
done
|
||||
if [ ${#providers[@]} -eq 0 ]; then
|
||||
err "No AI providers installed — run 'pos ai' setup first"
|
||||
fi
|
||||
menu_pick "Pick provider" "${providers[@]}"
|
||||
}
|
||||
|
||||
_alias_find() {
|
||||
local name="$1" i
|
||||
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
|
||||
if [ "${_ALIAS_NAMES[$i]}" = "$name" ]; then
|
||||
echo "$i"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "-1"
|
||||
return 0
|
||||
}
|
||||
|
||||
_alias_name_valid() {
|
||||
[[ "$1" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]
|
||||
}
|
||||
|
||||
_alias_prompt_truncate() {
|
||||
local p="$1"
|
||||
if [ ${#p} -gt 42 ]; then
|
||||
printf '%s…' "${p:0:42}"
|
||||
else
|
||||
printf '%s' "$p"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Non-interactive output ─────────────────────────────────────
|
||||
|
||||
_alias_list() {
|
||||
_alias_load
|
||||
local count=${#_ALIAS_NAMES[@]}
|
||||
printf 'Aliases (%d):\n' "$count"
|
||||
[ "$count" -eq 0 ] && return 0
|
||||
printf ' %-12s %-12s %-12s %s\n' "Name" "Provider" "Session" "Prompt"
|
||||
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
|
||||
"------------------------------------------"
|
||||
local i
|
||||
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]}")"
|
||||
done
|
||||
}
|
||||
|
||||
_alias_show() {
|
||||
_alias_load
|
||||
local idx
|
||||
idx="$(_alias_find "$1")"
|
||||
[ "$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]}"
|
||||
[ -z "$session" ] && session="$name"
|
||||
printf ' %-12s %s\n' "Alias:" "$name"
|
||||
printf ' %-12s %s\n' "Provider:" "$provider"
|
||||
printf ' %-12s %s\n' "Session:" "$session"
|
||||
printf ' %-12s %s\n' "Prompt:" "${prompt:-$(printf '%s' "(default)")}"
|
||||
# Show the resolved command
|
||||
local cmd="pos ai $provider ask --session $session"
|
||||
if [ -n "$prompt" ]; then
|
||||
local escaped="${prompt//\'/\'\\\'\'}"
|
||||
cmd="$cmd --system '$escaped'"
|
||||
fi
|
||||
printf ' %-12s %s\n' "Command:" "$cmd"
|
||||
}
|
||||
|
||||
# ── Interactive: main menu ─────────────────────────────────────
|
||||
|
||||
_alias_menu() {
|
||||
menu_guard || return 1
|
||||
while true; do
|
||||
{
|
||||
_alias_load
|
||||
if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then
|
||||
echo "${YELLOW}[!] No aliases defined yet — create one with option 1.${RESET}"
|
||||
else
|
||||
local count=${#_ALIAS_NAMES[@]} i
|
||||
printf ' %-12s %-12s %-12s %s\n' "Name" "Provider" "Session" "Prompt"
|
||||
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
|
||||
"------------------------------------------"
|
||||
for ((i = 0; i < count; i++)); do
|
||||
local p="${_ALIAS_PROMPTS[$i]}"
|
||||
if [ ${#p} -gt 42 ]; then
|
||||
p="${p:0:42}…"
|
||||
fi
|
||||
printf ' %-12s %-12s %-12s %s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \
|
||||
"${_ALIAS_SESSIONS[$i]}" "$p"
|
||||
done
|
||||
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
|
||||
"------------------------------------------"
|
||||
printf ' %d alias(es)\n' "$count"
|
||||
fi
|
||||
echo >&2
|
||||
} >&2
|
||||
local choice
|
||||
choice="$(menu_run "AI Agent Aliases" "Create new alias" "Edit existing alias" \
|
||||
"Remove alias" "List aliases")" || return 0
|
||||
case "$choice" in
|
||||
1) _alias_create ;;
|
||||
2) _alias_edit ;;
|
||||
3) _alias_remove ;;
|
||||
4) _alias_list ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ── Interactive: create ────────────────────────────────────────
|
||||
|
||||
_alias_create() {
|
||||
local preset_name="${1:-}"
|
||||
section "Create AI Agent Alias" >&2
|
||||
|
||||
# Step 1: Alias name
|
||||
local name="$preset_name"
|
||||
while true; do
|
||||
if [ -z "$name" ]; then
|
||||
step 1 4 "Alias Name" >&2
|
||||
name="$(menu_ask_value "Alias name" "")" || return 0
|
||||
fi
|
||||
[ -z "$name" ] && { warn "Alias name cannot be empty" >&2; name=""; continue; }
|
||||
if ! _alias_name_valid "$name"; then
|
||||
warn "Invalid name '$name' — use letters, digits, hyphens, underscores (start with a letter)" >&2
|
||||
name=""; continue
|
||||
fi
|
||||
_alias_load
|
||||
local existing
|
||||
existing="$(_alias_find "$name")"
|
||||
if [ "$existing" != "-1" ]; then
|
||||
warn "Alias '$name' already exists — use 'pos ai alias edit $name' instead" >&2
|
||||
[ -n "$preset_name" ] && return 1
|
||||
name=""; continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
||||
# Step 2: Provider
|
||||
step 2 4 "Provider" >&2
|
||||
local pidx
|
||||
pidx="$(_alias_provider_pick)" || return 0
|
||||
local providers=()
|
||||
for f in "$PROVIDER_DIR"/*.sh; do
|
||||
[ -f "$f" ] || continue
|
||||
providers+=("$(basename "$f" .sh)")
|
||||
done
|
||||
local provider="${providers[$((pidx - 1))]}"
|
||||
|
||||
# Step 3: Session name
|
||||
local session=""
|
||||
while true; do
|
||||
step 3 4 "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
|
||||
session=""; continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
[ -z "$session" ] && session="$name"
|
||||
|
||||
# Step 4: System prompt
|
||||
local prompt=""
|
||||
while true; do
|
||||
step 4 4 "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
|
||||
prompt=""; continue
|
||||
fi
|
||||
if [ ${#prompt} -gt 500 ]; then
|
||||
warn "Prompt is ${#prompt} chars — consider keeping it concise" >&2
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
||||
# Confirmation
|
||||
{
|
||||
echo "────────────────────────────────────────────"
|
||||
printf ' Create alias '\''%s'\''?\n' "$name"
|
||||
printf ' Provider: %s\n' "$provider"
|
||||
printf ' Session: %s\n' "$session"
|
||||
local dp="$prompt"
|
||||
[ ${#dp} -gt 50 ] && dp="${dp:0:50}…"
|
||||
printf ' Prompt: %s\n' "${dp:-<built-in>}"
|
||||
echo "────────────────────────────────────────────"
|
||||
} >&2
|
||||
if ! confirm "Create alias '$name'?" y; then
|
||||
log "Aborted." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
_alias_load
|
||||
_ALIAS_NAMES+=("$name")
|
||||
_ALIAS_PROVIDERS+=("$provider")
|
||||
_ALIAS_SESSIONS+=("$session")
|
||||
_ALIAS_PROMPTS+=("$prompt")
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' created. Reload shell: source ~/.bashrc" >&2
|
||||
}
|
||||
|
||||
# ── Interactive: edit ──────────────────────────────────────────
|
||||
|
||||
_alias_edit() {
|
||||
local preset_name="${1:-}"
|
||||
_alias_load
|
||||
if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then
|
||||
warn "No aliases to edit — create one first" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local name="$preset_name"
|
||||
if [ -z "$name" ]; then
|
||||
section "Edit AI Agent Alias" >&2
|
||||
local display_items=() i
|
||||
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
|
||||
local p="${_ALIAS_PROMPTS[$i]}"
|
||||
if [ ${#p} -gt 30 ]; then
|
||||
p="${p:0:30}…"
|
||||
fi
|
||||
display_items+=("${_ALIAS_NAMES[$i]} [${_ALIAS_PROVIDERS[$i]}] ${p}")
|
||||
done
|
||||
local picked
|
||||
picked="$(menu_pick "Pick alias to edit" "${display_items[@]}")" || return 0
|
||||
name="${_ALIAS_NAMES[$((picked - 1))]}"
|
||||
fi
|
||||
|
||||
local idx
|
||||
idx="$(_alias_find "$name")"
|
||||
if [ "$idx" = "-1" ]; then
|
||||
err "Alias '$name' not found"
|
||||
fi
|
||||
|
||||
# Show current values
|
||||
{
|
||||
echo " Current values for '$name':"
|
||||
printf ' Provider: %s\n' "${_ALIAS_PROVIDERS[$idx]}"
|
||||
printf ' Session: %s\n' "${_ALIAS_SESSIONS[$idx]}"
|
||||
local cp="${_ALIAS_PROMPTS[$idx]}"
|
||||
[ -z "$cp" ] && cp="(default)"
|
||||
printf ' Prompt: %s\n' "$cp"
|
||||
echo >&2
|
||||
} >&2
|
||||
|
||||
local new_provider="${_ALIAS_PROVIDERS[$idx]}"
|
||||
local new_session="${_ALIAS_SESSIONS[$idx]}"
|
||||
local new_prompt="${_ALIAS_PROMPTS[$idx]}"
|
||||
local changed=0
|
||||
|
||||
# Edit provider
|
||||
step 1 3 "Provider" >&2
|
||||
local pidx
|
||||
pidx="$(_alias_provider_pick)" || return 0
|
||||
local providers=()
|
||||
for f in "$PROVIDER_DIR"/*.sh; do
|
||||
[ -f "$f" ] || continue
|
||||
providers+=("$(basename "$f" .sh)")
|
||||
done
|
||||
local picked_provider="${providers[$((pidx - 1))]}"
|
||||
if [ "$picked_provider" != "$new_provider" ]; then
|
||||
new_provider="$picked_provider"
|
||||
changed=1
|
||||
fi
|
||||
|
||||
# Edit session
|
||||
local tmp_session=""
|
||||
while true; do
|
||||
step 2 3 "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
|
||||
tmp_session=""; continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
[ -n "$tmp_session" ] && new_session="$tmp_session"
|
||||
[ "$new_session" != "${_ALIAS_SESSIONS[$idx]}" ] && changed=1
|
||||
|
||||
# Edit prompt
|
||||
local tmp_prompt=""
|
||||
while true; do
|
||||
step 3 3 "System Prompt" >&2
|
||||
local default_prompt="${_ALIAS_PROMPTS[$idx]}"
|
||||
[ ${#default_prompt} -gt 80 ] && default_prompt="${default_prompt:0:80}…"
|
||||
[ -z "$default_prompt" ] && default_prompt=""
|
||||
tmp_prompt="$(menu_ask_value "System prompt" "$default_prompt")" || return 0
|
||||
if [[ "$tmp_prompt" == *'|'* ]]; then
|
||||
warn "System prompt must not contain '|' characters" >&2
|
||||
tmp_prompt=""; continue
|
||||
fi
|
||||
if [ ${#tmp_prompt} -gt 500 ]; then
|
||||
warn "Prompt is ${#tmp_prompt} chars — consider keeping it concise" >&2
|
||||
fi
|
||||
break
|
||||
done
|
||||
# Keep full current if user pressed Enter (tmp_prompt = default_prompt value)
|
||||
if [ -n "$tmp_prompt" ]; then
|
||||
new_prompt="$tmp_prompt"
|
||||
fi
|
||||
[ "$new_prompt" != "${_ALIAS_PROMPTS[$idx]}" ] && changed=1
|
||||
|
||||
# No changes?
|
||||
if [ "$changed" -eq 0 ]; then
|
||||
log "No changes — nothing to save." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Show diff summary
|
||||
{
|
||||
echo "────────────────────────────────────────────"
|
||||
printf ' Save changes to '\''%s'\''?\n' "$name"
|
||||
local tag_p tag_s tag_pr
|
||||
[ "$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)"
|
||||
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="<built-in>"
|
||||
printf ' Prompt: %s %s\n' "${dp:0:40}" "$tag_pr"
|
||||
echo "────────────────────────────────────────────"
|
||||
} >&2
|
||||
if ! confirm "Save changes to '$name'?" y; then
|
||||
log "Discarded." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
_alias_load
|
||||
_ALIAS_PROVIDERS[$idx]="$new_provider"
|
||||
_ALIAS_SESSIONS[$idx]="$new_session"
|
||||
_ALIAS_PROMPTS[$idx]="$new_prompt"
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' updated." >&2
|
||||
}
|
||||
|
||||
# ── Interactive: remove ────────────────────────────────────────
|
||||
|
||||
_alias_remove() {
|
||||
local preset_name="${1:-}"
|
||||
_alias_load
|
||||
if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then
|
||||
warn "No aliases to remove" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local name="$preset_name"
|
||||
if [ -z "$name" ]; then
|
||||
section "Remove AI Agent Alias" >&2
|
||||
local display_items=() i
|
||||
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
|
||||
local p="${_ALIAS_PROMPTS[$i]}"
|
||||
if [ ${#p} -gt 30 ]; then
|
||||
p="${p:0:30}…"
|
||||
fi
|
||||
display_items+=("${_ALIAS_NAMES[$i]} [${_ALIAS_PROVIDERS[$i]}] ${p}")
|
||||
done
|
||||
local picked
|
||||
picked="$(menu_pick "Pick alias to remove" "${display_items[@]}")" || return 0
|
||||
name="${_ALIAS_NAMES[$((picked - 1))]}"
|
||||
fi
|
||||
|
||||
local idx
|
||||
idx="$(_alias_find "$name")"
|
||||
if [ "$idx" = "-1" ]; then
|
||||
err "Alias '$name' not found"
|
||||
fi
|
||||
|
||||
# Show alias detail
|
||||
{
|
||||
echo " Alias: $name"
|
||||
printf ' Provider: %s\n' "${_ALIAS_PROVIDERS[$idx]}"
|
||||
printf ' Session: %s\n' "${_ALIAS_SESSIONS[$idx]}"
|
||||
printf ' Prompt: %s\n' "${_ALIAS_PROMPTS[$idx]:-<built-in>}"
|
||||
echo >&2
|
||||
} >&2
|
||||
|
||||
if ! confirm "Remove alias '$name'? This cannot be undone." n; then
|
||||
log "Cancelled." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
_alias_load
|
||||
local new_names=() new_providers=() new_sessions=() new_prompts=() i
|
||||
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
|
||||
if [ "${_ALIAS_NAMES[$i]}" != "$name" ]; then
|
||||
new_names+=("${_ALIAS_NAMES[$i]}")
|
||||
new_providers+=("${_ALIAS_PROVIDERS[$i]}")
|
||||
new_sessions+=("${_ALIAS_SESSIONS[$i]}")
|
||||
new_prompts+=("${_ALIAS_PROMPTS[$i]}")
|
||||
fi
|
||||
done
|
||||
_ALIAS_NAMES=("${new_names[@]+"${new_names[@]}"}")
|
||||
_ALIAS_PROVIDERS=("${new_providers[@]+"${new_providers[@]}"}")
|
||||
_ALIAS_SESSIONS=("${new_sessions[@]+"${new_sessions[@]}"}")
|
||||
_ALIAS_PROMPTS=("${new_prompts[@]+"${new_prompts[@]}"}")
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' removed." >&2
|
||||
}
|
||||
|
||||
# ── show <name> ───────────────────────────────────────────────
|
||||
|
||||
# (defined above as _alias_show)
|
||||
|
||||
# ── Usage ──────────────────────────────────────────────────────
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
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.
|
||||
|
||||
Subcommands:
|
||||
(no args) Interactive menu
|
||||
create [name] Create a new alias (interactive prompts for each field)
|
||||
edit [name] Edit an existing alias (interactive, Enter = keep)
|
||||
remove [name] Remove an alias (interactive, default = no)
|
||||
list List all aliases (non-interactive, machine-readable)
|
||||
show <name> Show one alias's details
|
||||
|
||||
Options:
|
||||
-h|--help Show this help.
|
||||
|
||||
Examples:
|
||||
pos ai alias # interactive menu
|
||||
pos ai alias list # show all aliases
|
||||
pos ai alias create # interactive create
|
||||
pos ai alias create mybot # create 'mybot' alias
|
||||
pos ai alias edit mybot # edit the 'mybot' alias
|
||||
pos ai alias remove mybot # remove 'mybot' (with confirm)
|
||||
pos ai alias show mybot # show alias details
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Main dispatch ──────────────────────────────────────────────
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help) usage ;;
|
||||
create) shift; _alias_create "${1:-}" ;;
|
||||
edit) shift; _alias_edit "${1:-}" ;;
|
||||
remove) shift; _alias_remove "${1:-}" ;;
|
||||
list) _alias_list ;;
|
||||
show)
|
||||
[ -n "${2:-}" ] || err "Usage: pos ai alias show <name>"
|
||||
_alias_show "$2"
|
||||
;;
|
||||
"") _alias_menu ;;
|
||||
*) err "Unknown subcommand '$1' (use -h for help)" ;;
|
||||
esac
|
||||
@@ -3,6 +3,10 @@
|
||||
# Provider-specific: API call, auth, response parsing, models list
|
||||
# Part of the R8 provider-agnostic architecture (lib/ai-providers/).
|
||||
|
||||
# Provider-specific config variables (auto-discovered by pos config ai):
|
||||
# PROVIDER_CONFIG: AI_GEMINI_API_KEY=secret:Gemini API key from aistudio.google.com
|
||||
# PROVIDER_CONFIG: AI_GEMINI_MODEL=:Gemini model id (default: gemini-2.5-flash)
|
||||
|
||||
provider_name() { printf 'Google Gemini'; }
|
||||
provider_default_model() { printf 'gemini-2.5-flash'; }
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
# Provider-specific: API call, auth, response parsing, models list
|
||||
# Part of the R8 provider-agnostic architecture (lib/ai-providers/).
|
||||
|
||||
# Provider-specific config variables (auto-discovered by pos config ai):
|
||||
# PROVIDER_CONFIG: OPENROUTER_API_KEY=secret:OpenRouter API key from openrouter.ai
|
||||
# PROVIDER_CONFIG: OPENROUTER_MODEL=:OpenRouter model id (default: openrouter/auto)
|
||||
|
||||
provider_name() { printf 'OpenRouter'; }
|
||||
provider_default_model() { printf 'openrouter/auto'; }
|
||||
|
||||
|
||||
+38
-1
@@ -136,6 +136,42 @@ _cfg_plugin_keys() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# "*providers" expansion: keys declared by the installed AI provider
|
||||
# adapters' "# PROVIDER_CONFIG:" headers (lib/ai-providers/*.sh).
|
||||
_cfg_provider_keys() {
|
||||
local pdir line key desc flags
|
||||
# Repo layout: lib/config-ui.sh → ../lib/ai-providers/
|
||||
# Installed layout: /usr/local/bin/config-ui.sh → ./ai-providers/
|
||||
pdir=""
|
||||
for candidate in \
|
||||
"$(dirname "${BASH_SOURCE[0]}")/../lib/ai-providers" \
|
||||
"$(dirname "${BASH_SOURCE[0]}")/ai-providers"; do
|
||||
if [ -d "$candidate" ]; then
|
||||
pdir="$(cd "$candidate" 2>/dev/null && pwd)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[ -n "$pdir" ] || return 0
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
# Format: KEY=flags:description (same as POS_CONFIG key fields)
|
||||
key="${line%%=*}"
|
||||
[ -n "$key" ] || continue
|
||||
[ -n "${_cfg_seen[$key]:-}" ] && continue
|
||||
_cfg_seen[$key]=1
|
||||
# Parse flags and description from the rest
|
||||
local rest="${line#*=}" flags="" desc=""
|
||||
if [[ "$rest" == *":"* ]]; then
|
||||
flags="${rest%%:*}"
|
||||
desc="${rest#*:}"
|
||||
else
|
||||
flags="$rest"
|
||||
fi
|
||||
printf '%s|%s|%s|\n' "$key" "$flags" "$desc"
|
||||
done < <(grep '^# PROVIDER_CONFIG:' "$pdir"/*.sh 2>/dev/null | sed 's/^.*# PROVIDER_CONFIG:[[:space:]]*//' || true)
|
||||
return 0
|
||||
}
|
||||
|
||||
# Declared keys for a scope: "KEY|flags|description" lines, deduped.
|
||||
cfg_scope_keys() {
|
||||
local scope="$1" dir line s keystring field
|
||||
@@ -155,7 +191,8 @@ cfg_scope_keys() {
|
||||
if [ -n "$field" ]; then
|
||||
if [[ "$field" == "*"* ]]; then
|
||||
case "$field" in
|
||||
*plugins*) _cfg_plugin_keys ;;
|
||||
*plugins*) _cfg_plugin_keys ;;
|
||||
*providers*) _cfg_provider_keys ;;
|
||||
esac
|
||||
else
|
||||
_cfg_key_line "$field"
|
||||
|
||||
@@ -39,6 +39,7 @@ PACKAGES=(
|
||||
python3 python3-pip rclone
|
||||
ffmpeg
|
||||
libqrencode4 libgtk-3-0 adb
|
||||
xdotool xclip
|
||||
)
|
||||
|
||||
spawn "apt update" sudo apt update
|
||||
|
||||
Reference in New Issue
Block a user