feat: command registry, alias wrapper scripts, config-ui readability
gates / consistency-and-conventions (push) Successful in 1m28s
gates / consistency-and-conventions (push) Successful in 1m28s
- lib/registry.sh: shared query API over POS_* headers (reg_scan, reg_list, reg_lookup, reg_tools_in, reg_each, reg_config_scopes/keys). Replaces per-consumer sed/grep header parsing. - bin/pos-tree + bin/pos _pos_category_help(): migrated to registry API. Category help now shows [deps: ...] annotations. Tree output preserved. - New optional headers # POS_DEPS: and # POS_EXAMPLES: in tool metadata. Added to pos-network-download (aria2c jq curl), pos-media-sync (lsblk jq), pos-system-backup (tar), pos-docker-ps (docker) as initial adopters. - scripts/gen-docs.sh: extended tools array with deps/examples fields; conditional column rendering in gen_dispatch; deps annotation in gen_tree. Fixed URL-unsafe // joiner (→ middle dot ·) and \x1f caption delimiter collision in config-ui. - bin/pos-ai-alias: rewrote activation from bash aliases (source-time-frozen) to executable wrapper scripts at ~/.local/bin. Staleness eliminated: edits apply on next invocation with no shell reload. _alias_sync() reconciliation on every subcommand, marker-guarded lifecycle, collision refusal, legacy .sh retirement. Fixed dup-table bug (option 4 no-op). - lib/config-ui.sh: @caption/@[KEY=alt] conditional captions, *providers=<tag> tagged wildcards, uniform typography tier (bold/cyan/dim), honest prompt. Active provider keys bold, inactive dimmed with reason. Backward-compatible. - bin/pos-system-uninstall: marker-scan for wrapper script cleanup. - Docs synced: AGENTS.md (new headers + registry), DOC/SCRIPTS.md (registry section + lib list), DOC/POS.md (alias wrapper activation), MAINTENANCE.md (M-024). Lint fixed: pos-ai-alias registered in INTERACTIVE_CMDS. Gates: make gen && make check && make lint = 0 FAIL, 0 WARN
This commit is contained in:
@@ -66,21 +66,27 @@ _pos_category_exists() {
|
||||
}
|
||||
|
||||
_pos_category_help() {
|
||||
local cat="$1" f
|
||||
local files=() s d
|
||||
for f in "$self"/pos-"$cat"-*; do
|
||||
[ -x "$f" ] || continue
|
||||
files+=("${f##*/pos-$cat-}")
|
||||
done
|
||||
local cat="$1" s s2 extra d sc deps
|
||||
# Lazy registry load — plain dispatch paths never pay the scan cost.
|
||||
source "$self/../lib/registry.sh" 2>/dev/null || source "$self/registry.sh"
|
||||
reg_scan "$self"
|
||||
|
||||
local files=()
|
||||
local t
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] || continue
|
||||
files+=("${t#"$cat"-}")
|
||||
done < <(reg_tools_in "$cat")
|
||||
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
|
||||
|
||||
local -A desc subcmds
|
||||
local sc
|
||||
local -A desc subcmds _deps
|
||||
for s in "${files[@]}"; do
|
||||
d="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$self/pos-$cat-$s" 2>/dev/null)"
|
||||
[ -n "$d" ] && desc["$s"]="${d#*— }"
|
||||
sc="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$self/pos-$cat-$s" 2>/dev/null)"
|
||||
d="$(reg_lookup "$cat-$s" desc)"
|
||||
[ -n "$d" ] && desc["$s"]="$d"
|
||||
sc="$(reg_lookup "$cat-$s" subcmds)"
|
||||
[ -n "$sc" ] && subcmds["$s"]="$sc"
|
||||
deps="$(reg_lookup "$cat-$s" deps)"
|
||||
[ -n "$deps" ] && _deps["$s"]="$deps"
|
||||
done
|
||||
|
||||
# Nested sub-tools: pos-<cat>-<a>-<b> lists "b" under <a>.
|
||||
@@ -113,6 +119,7 @@ _pos_category_help() {
|
||||
done
|
||||
[ "$is_nested" -eq 1 ] && continue
|
||||
printf ' %-28s%s\n' "$s" "${desc[$s]:-}"
|
||||
[ -n "${_deps[$s]:-}" ] && printf ' [deps: %s]\n' "${_deps[$s]}"
|
||||
for c in ${subcmds[$s]:-}; do
|
||||
printf ' %s %s\n' "$s" "$c"
|
||||
done
|
||||
@@ -259,7 +266,7 @@ MAIN_LOG="$LOG_DIR/pos.log"
|
||||
log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; }
|
||||
|
||||
# Commands that read from stdin interactively — only log invocation
|
||||
INTERACTIVE_CMDS="docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-sync system-backup system-uninstall share-usb-server share-smb-server share-smb-client share-nfs-client share-nfs-server communication-telegram-listener communication-matrix-listener ai ai-gemini ai-openrouter system-schedule entertainment-config config"
|
||||
INTERACTIVE_CMDS="docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-sync system-backup system-uninstall share-usb-server share-smb-server share-smb-client share-nfs-client share-nfs-server communication-telegram-listener communication-matrix-listener ai ai-gemini ai-openrouter ai-alias system-schedule entertainment-config config"
|
||||
|
||||
for ((i=n-1; i>=0; i--)); do
|
||||
cmd="pos"
|
||||
|
||||
+1
-1
@@ -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) | *providers | 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) | @[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"
|
||||
|
||||
|
||||
+180
-64
@@ -48,35 +48,134 @@ _alias_save() {
|
||||
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)
|
||||
# Build the pos-ai command for an alias with the prompt safely quoted as
|
||||
# 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
|
||||
printf -v q_prompt '%q' "$prompt"
|
||||
printf 'pos ai %s ask --session %s' "$provider" "$session"
|
||||
[ -n "$prompt" ] && printf ' --system %s' "$q_prompt"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Activation artifacts (Option B) ────────────────────────────
|
||||
# ENV stays the single source of truth; each alias is materialized as an
|
||||
# executable wrapper script at ~/.local/bin/<name>. Every invocation re-reads
|
||||
# current bytes, so a stale snapshot (the old sourced-alias failure mode) is
|
||||
# impossible by construction. No shell sourcing of any kind.
|
||||
|
||||
_wrapper_path() {
|
||||
printf '%s/.local/bin/%s' "$HOME" "$1"
|
||||
}
|
||||
|
||||
# Ownership test: line 2 must carry our generator marker. Files failing this
|
||||
# test are NEVER overwritten or deleted.
|
||||
_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).
|
||||
# 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:-}"
|
||||
cat <<WRAPPER_EOF
|
||||
#!/usr/bin/env bash
|
||||
# Managed by pos ai alias — regenerated automatically; hand-edits are overwritten.
|
||||
# Alias: ${name} | provider: ${provider} | session: ${session}
|
||||
set -euo pipefail
|
||||
exec $(_alias_quote_cmd "$provider" "$session" "$prompt") "\$@"
|
||||
WRAPPER_EOF
|
||||
}
|
||||
|
||||
# Atomically install/refresh one wrapper. Skips the write when the rendered
|
||||
# content already matches (stable mtimes → sync idempotence is observable).
|
||||
# Pre-commit validation: bash -n on the rendered file; failure keeps previous.
|
||||
_wrapper_install() { # name provider session prompt
|
||||
local path="$(_wrapper_path "$1")" tmp
|
||||
tmp="$(mktemp "${HOME}/.local/bin/.pos-alias.XXXXXX")"
|
||||
_wrapper_render "$1" "$2" "$3" "$4" >"$tmp"
|
||||
if cmp -s "$tmp" "$path"; then
|
||||
rm -f "$tmp"
|
||||
return 0
|
||||
fi
|
||||
if ! bash -n "$tmp" 2>/dev/null; then
|
||||
warn "Generated alias file has syntax errors — keeping previous version"
|
||||
warn "Wrapper for '$1' failed syntax check — keeping previous version" >&2
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
mv "$tmp" "$SH_FILE"
|
||||
chmod 644 "$SH_FILE"
|
||||
mv "$tmp" "$path"
|
||||
chmod 755 "$path"
|
||||
}
|
||||
|
||||
# rc 0 iff ~/.local/bin is on PATH.
|
||||
_alias_check_path() {
|
||||
case ":$PATH:" in
|
||||
*":$HOME/.local/bin:"*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Legacy ~/.config/.../ai-aliases.sh retirement: activation moved to wrapper
|
||||
# scripts, and a stale sourced alias would shadow them (interactive bash gives
|
||||
# aliases precedence over PATH lookups). Marker-guarded auto-remove only —
|
||||
# foreign files are warned about and left untouched.
|
||||
_alias_retire_legacy_sh() {
|
||||
[ -f "$SH_FILE" ] || return 0
|
||||
if ! head -n 3 "$SH_FILE" | grep -q 'Auto-generated by pos ai alias'; then
|
||||
warn "$SH_FILE was not generated by pos ai alias — left untouched; review manually"
|
||||
return 0
|
||||
fi
|
||||
local stale
|
||||
stale="$(sed -n 's/^alias \([A-Za-z_][A-Za-z0-9_-]*\)=.*/\1/p' "$SH_FILE" | tr '\n' ' ')"
|
||||
stale="${stale% }"
|
||||
rm -f "$SH_FILE"
|
||||
{
|
||||
echo "[!] Alias activation moved to executable scripts in ~/.local/bin/ — legacy file removed: $SH_FILE"
|
||||
[ -n "$stale" ] && echo " Stale sourced aliases shadow the new scripts until cleaned — run: unalias $stale"
|
||||
echo " (or simply start a new shell)"
|
||||
} >&2
|
||||
return 0
|
||||
}
|
||||
|
||||
# Two-way reconciliation on EVERY invocation:
|
||||
# forward: each ENV entry → render-diff-install (first-run migration,
|
||||
# create/edit/remove consistency, silent heal of hand-edited wrappers)
|
||||
# reverse: owned wrappers whose name is not in ENV → deleted (covers remove,
|
||||
# manual ENV edits, and the empty-set case)
|
||||
# plus: legacy .sh retirement; PATH guidance when owned wrappers exist but
|
||||
# ~/.local/bin is absent from PATH (wrappers are written regardless).
|
||||
_alias_sync() {
|
||||
_alias_load
|
||||
local bin_dir="${HOME}/.local/bin" i name f base match any=0
|
||||
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]}" || :
|
||||
done
|
||||
for f in "$bin_dir"/*; do
|
||||
[ -f "$f" ] || continue
|
||||
_alias_owned "$f" || continue
|
||||
base="${f##*/}"
|
||||
match=0
|
||||
for name in ${_ALIAS_NAMES[@]+"${_ALIAS_NAMES[@]}"}; do
|
||||
[ "$base" = "$name" ] && { match=1; break; }
|
||||
done
|
||||
[ "$match" -eq 1 ] || rm -f "$f"
|
||||
done
|
||||
_alias_retire_legacy_sh
|
||||
if ! _alias_check_path; then
|
||||
for f in "$bin_dir"/*; do
|
||||
[ -f "$f" ] && _alias_owned "$f" && { any=1; break; }
|
||||
done
|
||||
if [ "$any" -eq 1 ]; then
|
||||
warn "~/.local/bin is not on your PATH — alias scripts will not resolve by name."
|
||||
warn " Fix now: export PATH=\"\$HOME/.local/bin:\$PATH\""
|
||||
warn " Persist it: echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.profile"
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_alias_provider_pick() {
|
||||
@@ -118,23 +217,27 @@ _alias_prompt_truncate() {
|
||||
|
||||
# ── Non-interactive output ─────────────────────────────────────
|
||||
|
||||
_alias_list() {
|
||||
_alias_load
|
||||
local count=${#_ALIAS_NAMES[@]}
|
||||
printf 'Aliases (%d):\n' "$count"
|
||||
# SINGLE alias-table renderer — used by `list` (stdout) and the menu
|
||||
# pre-render (inside its stderr display block). One source of truth for the
|
||||
# grid so the two contexts can never drift or duplicate each other.
|
||||
_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' "------------" "------------" "------------" \
|
||||
"------------------------------------------"
|
||||
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_list() {
|
||||
printf 'Aliases (%d):\n' "${#_ALIAS_NAMES[@]}"
|
||||
_alias_table
|
||||
}
|
||||
|
||||
_alias_show() {
|
||||
_alias_load
|
||||
local idx
|
||||
idx="$(_alias_find "$1")"
|
||||
[ "$idx" = "-1" ] && err "Alias '$1' not found"
|
||||
@@ -144,14 +247,15 @@ _alias_show() {
|
||||
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'"
|
||||
if _alias_check_path; then
|
||||
printf ' %-12s %s\n' "Wrapper:" "$(_wrapper_path "$name")"
|
||||
else
|
||||
printf ' %-12s %s\n' "Wrapper:" "(not installed — ~/.local/bin not on PATH)"
|
||||
fi
|
||||
printf ' %-12s %s\n' "Command:" "$cmd"
|
||||
printf ' %-12s %s\n' "Prompt:" "${prompt:-$(printf '%s' "(default)")}"
|
||||
# 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")"
|
||||
}
|
||||
|
||||
# ── Interactive: main menu ─────────────────────────────────────
|
||||
@@ -164,21 +268,8 @@ _alias_menu() {
|
||||
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"
|
||||
_alias_table
|
||||
printf ' %d alias(es)\n' "${#_ALIAS_NAMES[@]}"
|
||||
fi
|
||||
echo >&2
|
||||
} >&2
|
||||
@@ -189,7 +280,10 @@ _alias_menu() {
|
||||
1) _alias_create ;;
|
||||
2) _alias_edit ;;
|
||||
3) _alias_remove ;;
|
||||
4) _alias_list ;;
|
||||
4) : ;; # List aliases — the loop's pre-render above IS the current
|
||||
# table (single renderer, redrawn fresh every iteration);
|
||||
# option 4 returns to the loop for a fresh render instead
|
||||
# of printing a second copy (dup-table bug fix).
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -220,6 +314,17 @@ _alias_create() {
|
||||
[ -n "$preset_name" ] && return 1
|
||||
name=""; continue
|
||||
fi
|
||||
# Collision refusals (never clobber foreign files or real binaries):
|
||||
# 1. wrapper exists WITH our marker → fine, sync regenerates it
|
||||
# 2. file exists WITHOUT marker → refuse
|
||||
# 3. name resolves to another binary on PATH → refuse, naming it
|
||||
local wpath
|
||||
wpath="$(_wrapper_path "$name")"
|
||||
if [ -e "$wpath" ]; then
|
||||
_alias_owned "$wpath" || err "File '~/.local/bin/$name' already exists and was not created by pos ai alias — pick another name"
|
||||
elif command -v "$name" >/dev/null 2>&1; then
|
||||
err "'$name' already exists on PATH as $(command -v "$name") — pick another name"
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
||||
@@ -284,8 +389,9 @@ _alias_create() {
|
||||
_ALIAS_SESSIONS+=("$session")
|
||||
_ALIAS_PROMPTS+=("$prompt")
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' created. Reload shell: source ~/.bashrc" >&2
|
||||
_alias_sync
|
||||
log "Alias '$name' created." >&2
|
||||
log "Available immediately: $(_wrapper_path "$name")" >&2
|
||||
}
|
||||
|
||||
# ── Interactive: edit ──────────────────────────────────────────
|
||||
@@ -419,8 +525,8 @@ _alias_edit() {
|
||||
_ALIAS_SESSIONS[$idx]="$new_session"
|
||||
_ALIAS_PROMPTS[$idx]="$new_prompt"
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' updated." >&2
|
||||
_alias_sync
|
||||
log "Alias '$name' updated — the change is live on next invocation." >&2
|
||||
}
|
||||
|
||||
# ── Interactive: remove ────────────────────────────────────────
|
||||
@@ -484,8 +590,9 @@ _alias_remove() {
|
||||
_ALIAS_SESSIONS=("${new_sessions[@]+"${new_sessions[@]}"}")
|
||||
_ALIAS_PROMPTS=("${new_prompts[@]+"${new_prompts[@]}"}")
|
||||
_alias_save
|
||||
_alias_regen
|
||||
log "Alias '$name' removed." >&2
|
||||
_alias_sync
|
||||
log "Alias '$name' removed — script deleted from $(_wrapper_path "$name")." >&2
|
||||
log "If the name still autocompletes stale in this shell, run: hash -r" >&2
|
||||
}
|
||||
|
||||
# ── show <name> ───────────────────────────────────────────────
|
||||
@@ -510,6 +617,12 @@ Subcommands:
|
||||
list List all aliases (non-interactive, machine-readable)
|
||||
show <name> Show one alias's details
|
||||
|
||||
Activation: every alias is materialized as an executable script at
|
||||
~/.local/bin/<name>, synced automatically on every invocation — no shell
|
||||
sourcing required. Changes are live on the next invocation, and the
|
||||
scripts work identically in interactive shells, scripts, cron, and
|
||||
non-login ssh sessions.
|
||||
|
||||
Options:
|
||||
-h|--help Show this help.
|
||||
|
||||
@@ -526,17 +639,20 @@ EOF
|
||||
}
|
||||
|
||||
# ── Main dispatch ──────────────────────────────────────────────
|
||||
# Every subcommand syncs first: artifacts always equal ENV truth before any
|
||||
# subcommand logic runs (migration, healing, retraction — all automatic).
|
||||
|
||||
case "${1:-}" in
|
||||
-h|--help) usage ;;
|
||||
create) shift; _alias_create "${1:-}" ;;
|
||||
edit) shift; _alias_edit "${1:-}" ;;
|
||||
remove) shift; _alias_remove "${1:-}" ;;
|
||||
list) _alias_list ;;
|
||||
create) shift; _alias_sync; _alias_create "${1:-}" ;;
|
||||
edit) shift; _alias_sync; _alias_edit "${1:-}" ;;
|
||||
remove) shift; _alias_sync; _alias_remove "${1:-}" ;;
|
||||
list) _alias_sync; _alias_list ;;
|
||||
show)
|
||||
[ -n "${2:-}" ] || err "Usage: pos ai alias show <name>"
|
||||
_alias_sync
|
||||
_alias_show "$2"
|
||||
;;
|
||||
"") _alias_menu ;;
|
||||
"") _alias_sync; _alias_menu ;;
|
||||
*) err "Unknown subcommand '$1' (use -h for help)" ;;
|
||||
esac
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker ps — Enhanced container overview (health, IPs, ports, uptime)
|
||||
# POS_DEPS: docker
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
|
||||
@@ -3,6 +3,9 @@ set -euo pipefail
|
||||
# POS: media sync — Incremental Music → USB sync (mp3/mp4, add/update only)
|
||||
# POS_FLAGS: --mp3 --mp4 --source --dry-run
|
||||
# POS_SUBCMDS: menu
|
||||
# POS_DEPS: lsblk jq
|
||||
# POS_EXAMPLES: pos media sync --mp3 | Sync only MP3 files to USB
|
||||
# POS_EXAMPLES: pos media sync --mp4 --dry-run | Preview MP4 sync without copying
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
||||
|
||||
@@ -3,6 +3,10 @@ set -euo pipefail
|
||||
# POS: network download — aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits)
|
||||
# POS_SUBCMDS: start stop status add torrent metalink list info files peers pause resume remove purge move limit set watch restart retry replace menu
|
||||
# POS_FLAGS: --dir --out --split --seed --force --upload --gid --tmux
|
||||
# POS_DEPS: aria2c jq curl
|
||||
# POS_EXAMPLES: pos network download add https://example.com/file.zip | Enqueue an HTTP download (auto-starts daemon)
|
||||
# POS_EXAMPLES: pos network download status | Daemon health + global transfer stats
|
||||
# POS_EXAMPLES: pos network download watch | Live progress view
|
||||
|
||||
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"
|
||||
|
||||
@@ -4,6 +4,7 @@ set -euo pipefail
|
||||
# POS_FLAGS: --service --no-encrypt
|
||||
# POS_SUBCMDS: menu
|
||||
# POS_CONFIG: notify | notify.env | NOTIFY_PLATFORM=:Comma-separated notify platforms (default telegram) — shared by backup, firewall, share nfs client/server
|
||||
# POS_DEPS: tar
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
||||
|
||||
@@ -95,6 +95,15 @@ scan_tier1() {
|
||||
# User-local binaries
|
||||
[ -f "$HOME/.local/bin/pos-ai-hook.sh" ] && found+=("$HOME/.local/bin/pos-ai-hook.sh")
|
||||
|
||||
# pos ai alias wrapper scripts (marker-managed)
|
||||
if [ -d "$HOME/.local/bin" ]; then
|
||||
local awf
|
||||
for awf in "$HOME/.local/bin"/*; do
|
||||
[ -f "$awf" ] || continue
|
||||
grep -q 'Managed by pos ai alias' "$awf" 2>/dev/null && found+=("$awf")
|
||||
done
|
||||
fi
|
||||
|
||||
# Completion file
|
||||
[ -f /usr/local/share/bash-completion/completions/pos.bash ] && found+=("/usr/local/share/bash-completion/completions/pos.bash")
|
||||
|
||||
@@ -260,6 +269,17 @@ remove_tier1() {
|
||||
# User-local binaries
|
||||
[ -f "$HOME/.local/bin/pos-ai-hook.sh" ] && { rm -f "$HOME/.local/bin/pos-ai-hook.sh" && count=$((count+1)); }
|
||||
|
||||
# pos ai alias wrapper scripts (marker-managed only — foreign files untouched)
|
||||
if [ -d "$HOME/.local/bin" ]; then
|
||||
local arwf
|
||||
for arwf in "$HOME/.local/bin"/*; do
|
||||
[ -f "$arwf" ] || continue
|
||||
if grep -q 'Managed by pos ai alias' "$arwf" 2>/dev/null; then
|
||||
rm -f "$arwf" && count=$((count+1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Completion file
|
||||
[ -f /usr/local/share/bash-completion/completions/pos.bash ] && { rm -f /usr/local/share/bash-completion/completions/pos.bash && count=$((count+1)); }
|
||||
|
||||
|
||||
+12
-6
@@ -4,6 +4,7 @@ set -euo pipefail
|
||||
# POS_FLAGS: --depth
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
source "$(dirname "$0")/../lib/registry.sh" 2>/dev/null || source "$(dirname "$0")/registry.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@@ -44,12 +45,17 @@ add() {
|
||||
meta["$path"]="$type|$desc"
|
||||
}
|
||||
|
||||
for f in "$self"/pos-*; do
|
||||
[ -x "$f" ] || continue
|
||||
name="${f##*/pos-}"
|
||||
desc="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$f")"
|
||||
desc="${desc#*— }"
|
||||
sc="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$f")"
|
||||
reg_scan "$self"
|
||||
|
||||
for tool_key in $(reg_list); do
|
||||
cat="$(reg_lookup "$tool_key" cat)"
|
||||
desc="$(reg_lookup "$tool_key" desc)"
|
||||
sc="$(reg_lookup "$tool_key" subcmds)"
|
||||
deps="$(reg_lookup "$tool_key" deps)"
|
||||
name="${tool_key}"
|
||||
if [ -n "$deps" ]; then
|
||||
desc="$desc [deps: $deps]"
|
||||
fi
|
||||
words=(${name//-/ })
|
||||
parent="/"
|
||||
for ((i=0; i<${#words[@]}; i++)); do
|
||||
|
||||
Reference in New Issue
Block a user